【发布时间】:2022-10-31 07:37:59
【问题描述】:
我正在尝试使用 Apple 的 App Store Server API 来获取有关在我的 iOS 应用中进行的应用内购买的交易信息。他们的服务器 api 使用 JWT 来传输/签署数据。我能够成功地从 Apple 获取包含一组签名交易 JWT 的数据:
{
"status":0,
"signedTransactions":[
"eyJhbGciOiJFUzI1NiIsIng1YyI6WyJNSUlFTURDQ0E3YWdBd0lCQWdJUWF..."
]
}
到目前为止,一切都很好。但是,当我尝试使用 Firebase 的 php-jwt 库对签名的交易 JWT 进行解码时,我遇到了致命错误。我首先尝试了 Firebase 的 php-jwt 库中的示例代码:
$signedTransactionJWT = $response['signedTransactions'][0];
$privateKeyText = file_get_contents('/private/key/from/appstoreconnect.p8');
$decodedTransactionPayload = JWT::decode($signedTransactionJWT, new Key($privateKeyText, 'ES256'));
但这给了我:
openssl_verify(): supplied key param cannot be coerced into a public key
后来在网上搜索了一堆关于 Apple 公钥的信息,我尝试使用 Apple 网站上发布的 auth 密钥:
$signedTransactionJWT = $response['signedTransactions'][0];
$appleKeysText = file_get_contents('/file/downloaded/from https://appleid.apple.com/auth/keys');
$jwks = json_decode($appleKeysText, true);
$keyset = JWK::parseKeySet($jwks);
$decodedTransactionPayload = JWT::decode($signedTransactionJWT, $keyset);
...但它会出现以下错误:
Fatal error: Uncaught UnexpectedValueException: "kid" empty, unable to lookup correct key
我查看了 JWT::decode() 方法,它在已签名交易 JWT 的标头中寻找密钥 ID(“kid”),但 Apple 并未在已签名交易的标头中提供“kid”智威汤逊。标头的结构如下所示:
{
"alg": "ES256",
"x5c": [
"MIIEMDCCA7agAwIBAgIQaPoPldvpSoEH0lBrjDPv9jAKBggqhkjOPQQDAzB1M...",
"MIIDFjCCApygAwIBAgIUIsGhRwp0c2nvU4YSycafPTjzbNcwCgYIKoZIzj0EA...",
"MIICQzCCAcmgAwIBAgIILcX8iNLFS5UwCgYIKoZIzj0EAwMwZzEbMBkGA1UEA..."
]
}
这是我第一次使用 JWT,所以我正在尽我所能理解这里的各种交互部分。根据有关 Apple 的 App Store Server API 的 WWDC 视频,标头的“x5c”部分应该用于在没有任何其他外部 Web 调用的情况下验证交易。所以,我觉得我不需要从https://appleid.apple.com/auth/keys 获取那些 JWT 密钥。据我了解,这个想法是签名应该是独立的。
如何正确解码来自 Apple 的 JWT,以便使用 Firebase 的 php-jwt 库验证有效负载?
更新
根据 Gary 的回答,我需要使用 x5c 数组中的第一项作为公钥。他提供了一些非常有用的链接和示例。希望这会引导我找到正确的答案,但我仍然遇到问题:
list($headerb64, $bodyb64, $cryptob64) = explode('.', $jwt);
$headertext = JWT::urlsafeB64Decode($headerb64);
$header = JWT::jsonDecode($headertext);
$keytext = $header->x5c[0];
$wrappedkeytext = trim(chunk_split($keytext, 64));
$publickey = <<<EOD
-----BEGIN PUBLIC KEY-----
$wrappedkeytext
-----END PUBLIC KEY-----
EOD;
print "public key:\n$publickey\n";
$decoded = JWT::decode($jwt, new Key($publickey, $header->alg));
按照指示,我解码了标题,抓取了第一项,将其转换为公钥格式的字符串,然后尝试使用它来解码$jwt,但我收到了这个错误:
警告:openssl_verify():提供的密钥参数不能被强制转换为 公钥
致命错误:未捕获的 DomainException:OpenSSL 错误: 错误:0909006C:PEM 例程:get_name:没有起始行
我打印出公钥字符串,以便确保我的格式正确。它看起来对我来说是正确的,但我对此很陌生,所以我可能会遗漏一些微妙的问题。起初,我尝试将内容全部放在一行上,但出现上述错误。然后我将其拆分为 64 个字符长的行,因为我发现一些文档说这些文本块的长度应限制为 64 个字符。但我仍然收到相同的错误消息。
-----BEGIN PUBLIC KEY----- MIIEMDCCA7agAwIBAgIQaPoPldvpSoEH0lBrjDPv9jAKBggqhkjOPQQDAzB1MUQw QgYDVQQDDDtBcHBsZSBXb3JsZHdpZGUgRGV2ZWxvcGVyIFJlbGF0aW9ucyBDZXJ0 aWZpY2F0aW9uIEF1dGhvcml0eTELMAkGA1UECwwCRzYxEzARBgNVBAoMCkFwcGxl IEluYy4xCzAJBgNVBAYTAlVTMB4XDTIxMDgyNTAyNTAzNFoXDTIzMDkyNDAyNTAz M1owgZIxQDA+BgNVBAMMN1Byb2QgRUNDIE1hYyBBcHAgU3RvcmUgYW5kIGlUdW5l cyBTdG9yZSBSZWNlaXB0IFNpZ25pbmcxLDAqBgNVBAsMI0FwcGxlIFdvcmxkd2lk ZSBEZXZlbG9wZXIgUmVsYXRpb25zMRMwEQYDVQQKDApBcHBsZSBJbmMuMQswCQYD VQQGEwJVUzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABOoTcaPcpeipNL9eQ06t Cu7pUcwdCXdN8vGqaUjd58Z8tLxiUC0dBeA+euMYggh1/5iAk+FMxUFmA2a1r4aC Z8SjggIIMIICBDAMBgNVHRMBAf8EAjAAMB8GA1UdIwQYMBaAFD8vlCNR01DJmig9 7bB85c+lkGKZMHAGCCsGAQUFBwEBBGQwYjAtBggrBgEFBQcwAoYhaHR0cDovL2Nl cnRzLmFwcGxlLmNvbS93d2RyZzYuZGVyMDEGCCsGAQUFBzABhiVodHRwOi8vb2Nz cC5hcHBsZS5jb20vb2NzcDAzLXd3ZHJnNjAyMIIBHgYDVR0gBIIBFTCCAREwggEN BgoqhkiG92NkBQYBMIH+MIHDBggrBgEFBQcCAjCBtgyBs1JlbGlhbmNlIG9uIHRo aXMgY2VydGlmaWNhdGUgYnkgYW55IHBhcnR5IGFzc3VtZXMgYWNjZXB0YW5jZSBv ZiB0aGUgdGhlbiBhcHBsaWNhYmxlIHN0YW5kYXJkIHRlcm1zIGFuZCBjb25kaXRp b25zIG9mIHVzZSwgY2VydGlmaWNhdGUgcG9saWN5IGFuZCBjZXJ0aWZpY2F0aW9u IHByYWN0aWNlIHN0YXRlbWVudHMuMDYGCCsGAQUFBwIBFipodHRwOi8vd3d3LmFw cGxlLmNvbS9jZXJ0aWZpY2F0ZWF1dGhvcml0eS8wHQYDVR0OBBYEFCOCmMBq//1L 5imvVmqX1oCYeqrMMA4GA1UdDwEB/wQEAwIHgDAQBgoqhkiG92NkBgsBBAIFADAK BggqhkjOPQQDAwNoADBlAjEAl4JB9GJHixP2nuibyU1k3wri5psGIxPME05sFKq7 hQuzvbeyBu82FozzxmbzpogoAjBLSFl0dZWIYl2ejPV+Di5fBnKPu8mymBQtoE/H 2bES0qAs8bNueU3CBjjh1lwnDsI= -----END PUBLIC KEY-----
【问题讨论】:
标签: php firebase jwt app-store-connect