苹果服务端通知v2处理(AppStore Server Notifications V2)
苹果服务端通知v2处理
关键词: App Store Server Notifications V2、Python源码、苹果订阅、JWS、x5c、JSON WEB TOKEN
背景
最近要接入苹果订阅功能,调研后发现订阅生命周期内的状态变更是通过苹果服务端通知返回的(什么时候普通内购也能加上减少掉单的概率),
其回调的正确性验证是依靠回调内容里的几个证书,捣鼓这块耗费了好几天时间,所以在此记录一下。
JWS
苹果服务端返回的数据格式为JWS,之前没处理过这种类型的数据,其实本质上还是JWT那一套,关于JWS的介绍
参考IETF RFC 7515-JSON Web Signature
JWS格式与验证
jws格式为 header.payload.signature
其中每部分都是基于urlbase64加密过的,需要使用urlbase64解密得到内容。
其中,header最为关键,其内容为:
{"alg": "ES256","x5c": ["服务器证书","中间证书","根证书"]
}
alg为本次回调的签名算法,ES256代表为 ECDSA using SHA-256 hash algorithm,
x5c则为证书链,其内部的第一个证书为验证本次回调签名所使用,需要先将证书转为X509格式,再从其中解析出公钥,使用公钥对数据进行验签
payload部分就是本次通知的业务数据,此处不做过多描述,参考官方文档
signature则是本次签名的结果。
简单的来说就是该格式包含了详细数据、证书链、签名算法、签名结果。
需要我们在本地完成数据的正确性和合法性:
- 合法性:验证证书链是可信的。
- 正确性:使用证书链中的服务器证书内的公钥验证数据和签名是正确未经过篡改的。
验证思路
x5c证书链的验证
这块参考了下图

也就是说,证书链内有三个证书,分别是服务器证书、中间证书、根证书。 其验证顺序是
- 使用中间证书验证服务器证书
- 使用根证书验证中间证书
- 使用根证书验证中间证书
那么根证书本身呢? 则需要用苹果官方的提供的根证书进行验证。如果整个验证流程下来都验证成功了,那么整个证书链就是可信的了。
其中苹果官方提供的根证书为AppleRootCA-G3.cer,需要自己从官网下载,下载地址
Python对证书的验证主要使用了openssl.crypto包下面的X509Store和X509StoreContext
其基本思路为
- 将可信证书(一般为root证书)先加载至X509Store实例内,然后使用X509StoreContext对待验证证书进行verify_certificate验证
- 单个证书验证如此,对于一个证书链,优先将待验证的证书验证完毕后加入到X509Store实例内,然后再继续验证后一个即可,以此类推。
python实现代码如下:
from OpenSSL import crypto
def verify_apple_jws_cert_chain(x5c):"""验证苹果server notify的证书链'x5c':['服务器证书','中间证书','根证书']我们验证顺序: 苹果根证书->x5c根证书, x5c根证书->中间证书, 中间证书->服务器证书:param x5c::return:"""if not x5c or not isinstance(x5c, list):return "x5c type error"# 加载x5c证书,转为X509证书格式x5c_cert = []try:for each in x5c:cert = "-----BEGIN CERTIFICATE-----\n" + each + "\n-----END CERTIFICATE-----"new_cert = crypto.load_certificate(crypto.FILETYPE_PEM, cert)x5c_cert.append(new_cert)except Exception as e:return "x5c certification load exception {}".format(e)# 加载苹果根证书cert_file = open("./AppleRootCA-G3.cer", "rb")apple_root_cert = crypto.load_certificate(crypto.FILETYPE_ASN1, cert_file.read())cert_file.close()# 接下来验证证书链,验证失败会报错:OpenSSL.crypto.X509StoreContextError: unable to get local issuer certificate# 首先验证x5c内的根证书store = crypto.X509Store()store.add_cert(apple_root_cert)try:store_ctx = crypto.X509StoreContext(store, x5c_cert[2])store_ctx.verify_certificate()except Exception as e:return "verify root certification exception {}".format(e)# 接下来验证x5c内的中间证书store.add_cert(x5c_cert[2])try:store_ctx = crypto.X509StoreContext(store, x5c_cert[1])store_ctx.verify_certificate()except Exception as e:return "verify mid certification exception {}".format(e)# 最后验证服务器证书store.add_cert(x5c_cert[1])try:store_ctx = crypto.X509StoreContext(store, x5c_cert[0])store_ctx.verify_certificate()except Exception as e:return "verify server certification exception {}".format(e)# 最终验证成功return ""
JWS的签名验证
验证完证书链后,那么签名的验证就好说了,目前的 jwt库 基本上都支持ES256签名了
不过我们需要先从x5c内获取服务器证书,将其转为X509对象后,获取其中的公钥,并使用公钥来验签,基本代码如下
import jwt
from OpenSSL import crypto# 获取服务器证书
alg = header.get("alg")
x5c = header.get("x5c")
server_cert = x5c[0]
# 将服务器证书转为X509证书对象
cert = "-----BEGIN CERTIFICATE-----\n" + server_cert + "\n-----END CERTIFICATE-----"
server_cert = crypto.load_certificate(crypto.FILETYPE_PEM, cert)
# 从证书内解析出公钥
public_key = crypto.dump_publickey(crypto.FILETYPE_PEM, server_cert.get_pubkey()).decode("utf-8")
# 使用公钥对整个jws进行验签
decode_jws = jwt.decode(jws, public_key, algorithms=[alg])
完整的代码和单元测试用例我放在我的github 上了
如果你觉得对你有帮助,希望帮忙点个star。
参考
StoreKit2【附源码】JWS X.509证书链验证
JWS-X.509 Certificate Chain
相关文章:
苹果服务端通知v2处理(AppStore Server Notifications V2)
苹果服务端通知v2处理 关键词: App Store Server Notifications V2、Python源码、苹果订阅、JWS、x5c、JSON WEB TOKEN 背景 最近要接入苹果订阅功能,调研后发现订阅生命周期内的状态变更是通过苹果服务端通知返回的(什么时候普通内购也能加上减少掉单的概率)&am…...
matlab 道路点云路缘石边界提取
目录 一、功能概述1、算法概述2、主要函数3、参考文献二、代码实现三、结果展示四、参考链接一、功能概述 1、算法概述 1、对于扫描线上的每个点,该函数计算这三个特征。 高差特征——计算一个点周围的标准偏差和高度最大差。路缘石点的标准偏差和高度差必须分别在指定的Heig…...
二叉树详解:带你掌握二叉树
目录 前言1. 树型结构1. 1 树的概念1.2 树的特点1.3 树的相关术语 2. 二叉树(binary tree)2.1 二叉树的概念2.2 二叉树中的特殊树2.2.1 满二叉树2.2.2 完全二叉树 2.3 二叉树的性质 3. 二叉树的遍历3.1 前序遍历3.2 中序遍历3.3 后序遍历3.4 层序遍历 总…...
LNMP网站框架搭建(编译安装)
目录 一、Nginx的工作原理 工作进程: 二、Nginx编译安装安装 三、mysql的编译安装 四、php的编译安装 验证PHP与nginx的是否连接 验证lnmp的是否搭建成功 五、部署 Discuz!社区论坛 一、Nginx的工作原理 php-fpm.conf 是控制php-fpm守护…...
详解Servlet API
目录 前言 HttpServlet HttpServletRequest 代码实例 打印请求信息 通过URL中的queryString进行传递。 通过post请求的body,使用form表单传递 通过POST 请求中的 body 按照 JSON 的格式进行传递 HttpServletResponse 核心方法代码实例 设置状态码 自动刷…...
【小白教程】Docker安装使用教程,以及常用命令!
【小白教程】Docker安装使用教程,以及常用命令! - 带你薅羊毛最近调试Docker内容,顺手记录一下,我常用的几个命令!这里总结一下,方便自己也同时方便大家使用! 内容慢慢完善更新!如有…...
TypeScript基础
TS编译运行 ts不是在终端运行,是一门中间语言,最终编译为js运行。 手动编译 // 1. ts编译为js npm i -g typescript // 查看版本 tsc -v// 2. ts直接运行,主要用来查看是否报错 npm i -g ts-node // 查看版本 ts-node -v1.手动编译ts代码 …...
QML学习二:Doxygen为qml工程生成代码文档
效果如下: 设置后能够支持.js和.qml文档。 QML学习二:Doxygen为工程生成注释文档 前言一、安装doxyqml二、Doxygen设置1.文档目录设置2.文档目录设置三、添加注释总结前言 好的代码必须配一个好的文档说明,方便以后维护以及学习。 前提条件: 1.安装好了Doxygen代码生成工…...
Vue 有哪些经典面试题?
前言 下面总结了vue的一些经典的面试题,希望对正在找工作面试的小伙伴们提供一些帮助,我们废话少说直接进入整体、 简述一下什么是MVVM模型 MVVM,是Model-View-ViewModel的简写,其本质是MVC模型的升级版。其中 Model 代表数据模…...
pandas速学-DataFrame
一、理解DataFrame 他是一个表格结构:DataFrame 是一个表格型的数据结构 他是有序的,不同值类型:它含有一组有序的列,每列可以是不同的值类型(数值、字符串、布尔型值)。 他可以被看做一个由series组成的…...
在任务与执行策略之间的隐性耦合
我们已经知道, Executor 框架可以将任务的提交与任务的执行策略解耦开来。就像许多对复杂过程的解耦操作那样,这种论断多少有些言过其实了。虽然Executor 框架为制定和修改执行策略都提供了相当大的灵活性,但并非所有的任务都能适用所有的执行…...
Spring Cloud Alibaba Nacos 构建配置中心
构建配置中心 新建命名空间 登录 Nacos 面板,依次点击左侧菜单栏【命名空间→新建命名空间】、填写命名空间名和描述信息,点击【确定】: 新建配置文件 依次点击左侧菜单栏【配置管理→配置列表】、切换到指定命名空间【此处为 shop】、点击…...
华为OD机试真题 Java 实现【猴子爬山】【2023 B卷 100分】,附详细解题思路
一、题目描述 一天一只顽猴想去从山脚爬到山顶,途中经过一个有个N个台阶的阶梯,但是这猴子有一个习惯: 每一次只能跳1步或跳3步,试问猴子通过这个阶梯有多少种不同的跳跃方式? 二、输入描述 输入只有一个整数N(0<N<=50)此阶梯有多少个阶梯。 三、输出描述 输…...
【19JavaScript for 循环】JavaScript for 循环:掌握重复执行的关键
JavaScript for 循环 在JavaScript中,for循环是一种常用的循环结构,它允许您重复执行一段代码,达到循环的目的。 基本语法 for (initialization; condition; iteration) {// 要执行的代码}for循环由以下几个关键部分组成: init…...
MySQL学习(联结,组合查询,全文本搜索)
联结 SQL最强大的功能之一就是能在数据检索查询的执行中联结表; 关系表 为什么要使用关系表? 使用关系表可以储存数据不重复,从而不浪费时间和空间;如果有数据信息变动,只需更新一个表中的单个记录,相关…...
Nautilus Chain:独特且纯粹的创新型 Layer3
以 Layer3 架构为主要特点的模块化公链 Nautilus Chain 即将在近期上线主网,这也进一步引发了行业关于 Layer3 的讨论。 实际上,在2022年以太坊的创始人 Vitalik 提出了三大目标:Layer2 用于扩展,Layer3 用于定制功能,…...
十六、立方体贴图(天空盒)
第一部分 概念: 1) 引用 OpenGL ES 立方体贴图本质上还是纹理映射,是一种 3D 纹理映射。立方体贴图所使的纹理称为立方图纹理,它是由 6 个单独的 2D 纹理组成,每个 2D 纹理是立方图的一个面。 立方图纹理的采样通过一个 3D 向量…...
UniAD:实现多类别异常检测的统一模型
来源:投稿 作者:Mr.Eraser 编辑:学姐 论文标题:用于多类异常检测的统一模型 论文链接:https://arxiv.org/abs/2206.03687 论文贡献: 提出UniAD,它以一个统一框架完成了多个类别的异常检测。 …...
Java 面试 | tcp ip http https(2023版)
文章目录 HTTP&HTTPS1、Http和Https的区别?2、什么是对称加密与非对称加密3、客户端不断进行请求链接会怎样?DDos(Distributed Denial of Service)攻击?4、GET 与 POST 的区别?5、什么是 HTTP 协议无状态协议?怎么解决Http协议无状态协议?6、Session、Cookie 与 Appl…...
全志V3S嵌入式驱动开发(音频输出和音频录制)
【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing 163.com】 之前在芯片公司的时候,基本没有看过音频这一块,只知道有个alsa框架这么个知识点。要驱动音频,需要两部分&#…...
5分钟搞懂3GPP NTN标准:从Release16到19的关键技术演进与实战应用
5分钟搞懂3GPP NTN标准:从Release16到19的关键技术演进与实战应用 当全球通信行业将目光投向低轨卫星星座与高空平台时,3GPP的NTN(非地面网络)标准正在重塑连接边界。本文将以工程师视角,带您穿透技术文档迷雾…...
项目分享|VibeVoice:微软开源的前沿语音AI
引言 在语音合成(TTS)技术领域,长篇幅、多说话者、低延迟的自然语音生成一直是行业痛点。传统TTS模型往往受限于生成时长、说话者数量或实时响应速度,难以满足播客制作、智能对话等复杂场景需求。微软开源的VibeVoice框架彻底打破…...
从‘各玩各的’到‘协同作战’:聊聊多传感器SLAM中坐标系对齐的那些‘坑’与最佳实践
从‘各玩各的’到‘协同作战’:多传感器SLAM坐标系对齐的工程实践指南 当激光雷达的轨迹点云与相机的视觉路径在三维空间中"貌合神离",工程师们往往面临一个关键抉择:是强行统一时间基准,还是重新建立空间映射关系&…...
别再死记硬背了!用Python脚本+Modbus Poll工具,5分钟搞懂Modbus功能码怎么用
用PythonModbus Poll实战:5分钟解锁功能码核心逻辑 第一次接触Modbus协议时,那些晦涩的功能码总让我头疼——01H、03H、05H这些十六进制代码就像天书,文档里的理论描述看完就忘。直到我发现用Python脚本配合Modbus Poll工具进行实操测试&…...
Spring Boot Helper插件免费版获取与版本适配全攻略
1. 为什么我们需要Spring Boot Helper插件 作为一个常年使用IntelliJ IDEA开发Spring Boot项目的程序员,我深刻体会到这个插件的重要性。简单来说,它就像是Spring Boot开发的"瑞士军刀",能帮我们快速创建项目、自动配置依赖、一键…...
手柄硬件校准与操控优化:从故障排查到竞技级设置的实战手册
手柄硬件校准与操控优化:从故障排查到竞技级设置的实战手册 【免费下载链接】DS4Windows Like those other ds4tools, but sexier 项目地址: https://gitcode.com/gh_mirrors/ds/DS4Windows 在《艾尔登法环》的 boss 战中,角色总是不受控制地缓慢…...
【算法对抗】打穿查重黑盒!论文降AI太难?8个实测有效策略与高性价比工具
上周匆匆写完论文初稿交给导师,结果被一眼识破,当场打回。还被导师认为不认真不负责态度不端正! 为了搞定这件事,我测评了市面上大部分的主流工具、试了无数方法,终于把AI率降到6%。 我们要先端正态度:论文…...
Delphi 终极实战:将自定义控件打包成 BPL,安装到 Delphi 工具栏(组件库实战)
前面我们手写了专属 UI 组件库(MyUIClass.pas),但如果你想在以后的项目中一键调用这些控件,而不是每次都复制粘贴代码,那就必须将它们打包成 Delphi 组件包(BPL 文件)。学会这篇,你将…...
2026 年直播电商如何进化?内容创作与管理的新模式是什么?
核心要点 问题: 为什么很多直播电商团队在 2025 年后明显感到"内容越来越多,但效果越来越不稳定"? 答案: 进入 2026 年,直播电商从"单场爆发"转向"内容体系竞争"。真正拉开差距的&#…...
从按键消抖到I2C通信:深入浅出聊聊MCU上拉/下拉电阻与开漏输出的那些坑
从按键消抖到I2C通信:深入浅出聊聊MCU上拉/下拉电阻与开漏输出的那些坑 在嵌入式系统开发中,GPIO配置看似简单,却暗藏玄机。记得第一次调试I2C总线时,通信速率始终上不去,最后发现竟是上拉电阻选型不当;另一…...
