结合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来做什么呢?
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
)
}
}
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
)
}
}
@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
{
if
(useAttestKey && !keyStore.containsAlias(attestKeyAlias)) {
generateKey(attestKeyAlias!!, useStrongBox, includeProps, attestKeyAlias)
}
generateKey(alias, useStrongBox, includeProps, attestKeyAlias)
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"
)
currentCerts = certs as List<X509Certificate>
return
parseCertificateChain(certs)
}
@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
{
if
(useAttestKey && !keyStore.containsAlias(attestKeyAlias)) {
generateKey(attestKeyAlias!!, useStrongBox, includeProps, attestKeyAlias)
}
generateKey(alias, useStrongBox, includeProps, attestKeyAlias)
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"
)
currentCerts = certs as List<X509Certificate>
return
parseCertificateChain(certs)
}
useStrongBox = pm.hasSystemFeature(PackageManager.FEATURE_STRONGBOX_KEYSTORE)
hasAttestKey = pm.hasSystemFeature(PackageManager.FEATURE_KEYSTORE_APP_ATTEST_KEY)
hasDeviceIds = pm.hasSystemFeature(
"android.software.device_id_attestation"
)
useStrongBox = pm.hasSystemFeature(PackageManager.FEATURE_STRONGBOX_KEYSTORE)
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
}
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)
}
}
val keyPairGenerator = KeyPairGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_EC,
"AndroidKeyStore"
)
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
}
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)
}
}
val keyPairGenerator = KeyPairGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_EC,
"AndroidKeyStore"
)
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))
}
}
public
static
AttestationResult parseCertificateChain(List<X509Certificate> certs) {
var infoList =
new
ArrayList<CertificateInfo>();
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);
public
static
AttestationResult parseCertificateChain(List<X509Certificate> certs) {
var infoList =
new
ArrayList<CertificateInfo>();
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);
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;
}
}
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;
}
}
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
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();
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();
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编辑
,原因: