在处理来自Apple的服务器到服务器通知时解码x5c证书链



我正在尝试处理来自苹果应用商店的服务器到服务器通知。下面的注释详细说明了我的发现,我欢迎任何需要的更正。

通知以JSON包的形式接收,并且可以从中提取"signedPayload"。signedPayload是JWT的形式,因此可以分为Header、Payload和Signature。

当报头是base64解码它的结果在另一个JSON数据包包含两个字段:" alg "one_answers" x5c "。其中algorithm字段的值为ES256, x5c字段包含由逗号分隔的三个证书的链。

负载可以被base64解码,然后根据这里的信息进行解释:https://developer.apple.com/documentation/appstoreservernotifications/responsebodyv2decodedpayload

那部分很简单。但是,在验证签名之前,不应操作有效负载。

签名将被保留以供后续处理。

回到证书链。链中的最后一个证书(证书3)是苹果的根证书AppleRootCA-G3证书。这可以从https://www.apple.com/certificateauthority/下载可以使用openssl将cer文件转换为pem文件,如下所示:

openssl x509 -inform der -in AppleRootCA-G3.cer -outform pem -out AppleRootCA-G3.pem

将pem文件与链中的证书3进行比较,将确认它们是相同的。

同样,链中的中间证书(证书2)为苹果的中间证书AppleWWDRCAG6.cer。将其转换为pem文件将确认它们是相同的。

链中的第一个证书(证书1)是实体证书。

根证书和中间证书可以使用openssl转换为文本,如下所示:

openssl x509 -text -in AppleRootCA-G3.pem

openssl x509 -text -in AppleWWDRCAG6.pem

这将显示每个证书的颁发者和主题。我明白,要使证书链有效,根的主体必须与中间的发行者相匹配,中间的主体必须与实体的发行者相匹配,根的主体和发行者必须相同。https://docs.apigee.com/how-to-guides/validating-certificate-chain splitcertchain

就是这样。到目前为止,一切顺利。

我不知道从现在开始该做什么。我的想法是,我需要完成证书链的验证,然后以某种方式计算一个签名,该签名需要与构成签名有效载荷一部分的签名相匹配。如何做到这一点呢?我也不清楚我是否需要苹果开发者账户的共享密钥,如果需要,它是如何使用的?编码算法ES256也必须在某个地方使用,但我不确定在哪里或如何使用。

来自Apple的POST包含一个"signedPayload",这是一个JWT -所以工作是验证JWT签名并在您充分确信它确实来自Apple后提取有效负载。

(免责声明:我是一个加密新手,所以如果我使用了错误的术语等,请检查我)

  1. JWT的头包含用于对其签名的证书链,因此首先提取这些公钥。这些都列在"x5c"属性为base64编码的asn1字节字符串。
  2. 然后,您想要验证a)链是有效的,b)链以您可以信任的众所周知的密钥开始。App Store的证书都是由苹果的root &中间键,你们已经有了。
  3. 中列出的应该是链中的最后两个证书。一旦你这样做了,你可以解码JWT通过传递有效的签名密钥给你方便的时髦的JWT解码库,它将根据头中的有效密钥验证它的签名。

这在python中有点棘手**,因为Apple正在使用一些格式和算法的组合,这会混淆PyJWT。以下是要点:https://gist.github.com/taylorhughes/3968575b40dd97f851f35892931ebf3e

* *委婉的

**不是CRYPTO专家**
我为此挣扎了一段时间,因为苹果的文档在如何验证签名令牌方面相当稀疏。

这个Apple Developer视频很有帮助(关于验证签名令牌的信息开始于~11m标记)。

我相信泰勒的整体步骤是准确的。以下是我最终在node:

中使用jsonwebtoken和crypto实现的示例代码:
const jwt = require('jsonwebtoken')
const fs = require('fs')
const crypto = require('crypto')
function decodePayload(token){
// G3 Cert here https://www.apple.com/certificateauthority/AppleRootCA-G3.cer
var apple_G3_cert = fs.readFileSync('./AppleRootCA-G3.cer') // TODO: download cert from apple
var x509G3 = new crypto.X509Certificate(apple_G3_cert)
// Decode the token to obtain the headers
var contents = jwt.decode(token,{complete: true})
// Create certs from the x5c array
var leaf_cert = new crypto.X509Certificate(Buffer.from(contents.header.x5c[0],'base64'));
var int_cert = new crypto.X509Certificate(Buffer.from(contents.header.x5c[1],'base64'));
var root_cert = new crypto.X509Certificate(Buffer.from(contents.header.x5c[2],'base64'));
// Validate the certs' issuers
if(leaf_cert.checkIssued(int_cert) && 
int_cert.checkIssued(root_cert) &&
root_cert.checkIssued(x509G3)){
// Chain is trusted - Verify signature
try{
return jwt.verify(token,leaf_cert.publicKey, {algorithms:'ES256'})
}
catch{
return null
}
}else{
return null
}
}

最新更新