You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用PHP从Apple App Attestation对象中提取并验证证书?

解决App Attestation服务端验证的证书解析与后续验证问题

我之前在做App Attestation服务端验证的时候,也踩过和你一模一样的坑,给你一步步梳理解决思路和具体操作:

1. 先解决x5c证书的读取问题

你拿到的$norm['attStmt']['x5c'][0]是DER格式的二进制证书,而openssl_x509_read()默认只识别带-----BEGIN CERTIFICATE-----头和尾的PEM格式证书,直接读取自然会失败。你需要先把DER格式转成PEM格式:

// 处理叶证书(x5c数组第一个元素,即credcert)
$derCert = $norm['attStmt']['x5c'][0];
// 把二进制DER证书转成Base64编码,再拼接PEM标准头尾
$pemCert = "-----BEGIN CERTIFICATE-----\n" . chunk_split(base64_encode($derCert), 64, "\n") . "-----END CERTIFICATE-----\n";

// 现在再读取证书就没问题了
$cert = openssl_x509_read($pemCert);
if ($cert === false) {
    // 打印OpenSSL错误信息排查问题
    echo openssl_error_string();
    exit;
}

$parsedCert = openssl_x509_parse($cert, false);

用同样的方法可以把x5c里的中间证书也转成PEM格式,方便后续操作。

2. 验证证书链的有效性

接下来要验证整个证书链的合法性:叶证书 → 中间证书 → Apple App Attest根证书。

操作步骤:

  • 提前获取Apple App Attest Root CA - G1的PEM格式证书,存到服务端。
  • 创建一个证书存储,把中间证书和根证书添加进去。
  • 用这个存储验证叶证书,确保它是Apple合法签发的。

示例代码:

// 把x5c[1]的中间证书转成PEM格式
$intermediateDer = $norm['attStmt']['x5c'][1];
$intermediatePem = "-----BEGIN CERTIFICATE-----\n" . chunk_split(base64_encode($intermediateDer), 64, "\n") . "-----END CERTIFICATE-----\n";

// Apple App Attest根证书的PEM内容(需要提前获取完整内容)
$rootPem = "-----BEGIN CERTIFICATE-----\nMIIB...(省略完整根证书内容)...-----END CERTIFICATE-----\n";

// 创建证书存储并添加证书
$certStore = openssl_x509_store_new();
openssl_x509_store_add_cert($certStore, openssl_x509_read($intermediatePem));
openssl_x509_store_add_cert($certStore, openssl_x509_read($rootPem));

// 验证叶证书
$verifyResult = openssl_x509_verify($cert, $certStore);
if ($verifyResult !== 1) {
    echo "证书链验证失败:" . openssl_error_string();
    exit;
}

3. 验证证书的关键扩展字段

证书链验证通过后,还要检查叶证书的特定扩展字段,确保它是针对你的App签发的:

  • App ID验证:查看证书的1.2.840.113635.100.8.2扩展字段,这个值是你的App ID的SHA-256哈希,要和你本地计算的App ID哈希完全一致。
  • Key ID验证:查看1.2.840.113635.100.8.1扩展字段,这个值要和authData里解析出的key ID匹配。

示例代码:

// 获取App ID扩展字段(二进制格式)
$appIdExtension = openssl_x509_parse($cert)['extensions']['1.2.840.113635.100.8.2'];
// 计算你的App ID的SHA-256哈希(比如你的App ID是com.yourcompany.yourapp)
$expectedAppIdHash = hash('sha256', 'com.yourcompany.yourapp', true);

if ($appIdExtension !== $expectedAppIdHash) {
    echo "App ID不匹配";
    exit;
}

4. 解析并验证authData

authData是CTAP2格式的二进制数据,里面包含了设备和凭证的关键信息,需要解析:

  • 前32字节:服务端域名(rpID)的SHA-256哈希,要和你的服务端域名哈希一致。
  • 第33字节:flags字段,用于标识凭证状态。
  • 第34-37字节:signCount,凭证的使用次数,用于防止重放攻击。
  • 第38字节起:attestedCredentialData,包含Apple固定的aaguid、credentialID、公钥等信息。

你可以自己编写解析逻辑,或者用专门的CTAP2解析库来处理。

5. 验证Attestation签名

最后一步是验证attStmt里的sig签名:

  • 构造待验证的原始数据:authData + clientDataHash(客户端发送的挑战字符串的SHA-256哈希)。
  • 用叶证书的公钥验证这个数据的签名(签名算法为ES256)。

示例代码:

// 假设你已经拿到客户端发送的挑战字符串clientData
$clientDataHash = hash('sha256', $clientData, true);
// 拼接待验证的原始数据
$verifyData = $norm['authData'] . $clientDataHash;
// 获取叶证书的公钥
$publicKey = openssl_pkey_get_public($cert);
// 验证签名(sig是二进制格式)
$verifyResult = openssl_verify($verifyData, $norm['attStmt']['sig'], $publicKey, OPENSSL_ALGO_SHA256);

if ($verifyResult !== 1) {
    echo "签名验证失败:" . openssl_error_string();
    exit;
}

调试小技巧

如果某一步失败,可以用OpenSSL命令行工具辅助排查:

  • 把DER转成PEM后,执行openssl x509 -in cert.pem -text -noout,查看证书内容是否正常解析。
  • 用openssl verify -CAfile root.pem -untrusted intermediate.pem leaf.pem验证证书链是否有效。

内容的提问来源于stack exchange,提问作者Leo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.30 16:47:47