北京网站优化解决方案,如何开通微信公众号,html5教程零基础,外包合同究竟能不能签公证方式
Mac 公证方式有三种 公证方法 优点 缺点 阐述 Xcode Xcode携带的图形界面#xff0c;使用方便 无法进行自动化公证 单个App应用上架使用较多 altool#xff08;旧版#xff09; 支持pkg#xff0c;dmg#xff0c;脚本自动化 2023/11/01 将会过期 已经…公证方式
Mac 公证方式有三种 公证方法 优点 缺点 阐述 Xcode Xcode携带的图形界面使用方便 无法进行自动化公证 单个App应用上架使用较多 altool旧版 支持pkgdmg脚本自动化 2023/11/01 将会过期 已经是弃用不建议使用 Mac开发-公证流程记录Notarization-附带脚本_xcrun altool --notarize-app --primary-bundle-id-CSDN博客 notarytool新版 支持pkgdmg脚本自动化 必须依赖macos环境 必须要依赖macos环境并且更新Xcode版本和mac版本有一定的环境限制 Notary API 支持pkgdmg脚本自动化 无需依赖macos环境 使用的是苹果官方提供的Web API进行公证不受运行环境限制
这里 Notary API 有较大的优势之前 altool 脚本公证的方式我们已经做过由于 2023/11/01 将被弃用考虑后续跨平台的需要使用 notary API 进行脚本自动化公证
https://developer.apple.com/documentation/notaryapi/submitting_software_for_notarization_over_the_web
流程
具体的流程大概如下
获取 API密钥 (Private Key)使用 API密钥 (Private Key) 生成 JSON Web Token (JWT) , 相当于授权令牌 token请求 Notary API 并在 HTTP 请求头中携带 JWT 内容得到请求结果 1. 获取 API密钥
https://developer.apple.com/documentation/appstoreconnectapi/creating_api_keys_for_app_store_connect_api
官方文档阐述
登录App Store Connect选择 [用户和访问] 这栏 然后选择API Keys选项卡单击生成API密钥或添加()按钮。输入密钥的名称。在Access下为密钥选择角色点击生成点击下载秘钥 注意下载后苹果不再保存你只能下载一次
根据上述流程获取你的 API 秘钥 2. 生成令牌 Token
https://developer.apple.com/documentation/appstoreconnectapi/generating_tokens_for_api_requests
2.1. 环境安装
根据 https://jwt.io/libraries 所述安装 pyjwt 库由于本地环境 python3, 使用 pip3 install pyjwt 安装, 并参考官网使用说明 https://pyjwt.readthedocs.io/en/stable/usage.html#encoding-decoding-tokens-with-hs256pyjwt 依赖 cryptography 库需要额外安装 pip3 install cryptography文件上传依赖 boto3 库pip3 install boto3 2.2. JWT Encode/Decode
参考 https://developer.apple.com/documentation/appstoreconnectapi/generating_tokens_for_api_requests 所述进行设置值得注意的是苹果会拒绝大多数设置过期时间为 20 分钟以上的 JWT 票据所以我们需要间隔生成 JWT
header
{alg: ES256,kid: 2X9R4HXF34,typ: JWT
}
body
{iss: 57246542-96fe-1a63-e053-0824d011072a,iat: 1528407600,exp: 1528408800,aud: appstoreconnect-v1,scope: [GET /notary/v2/submissions,POST /notary/v2/submissions,]
}
API 秘钥
对应的生成逻辑如下
def get_jwt_token():# 读取私钥文件内容with open(./AuthKey_xxxxx.p8, rb) as f:jwt_secret_key f.read()# 获取当前时间now datetime.datetime.now()# 计算过期时间当前时间往后 20 分钟expires now datetime.timedelta(minutes20)# 设置 JWT 的 headerjwt_header {alg: ES256,kid: 2X9R4HXF34,typ: JWT}# 检查文件是否存在if os.path.exists(./jwt_token):# 读取with open(./jwt_token, rb) as f:jwt_pre_token f.read()print([info],jwt token %s % (jwt_pre_token))try:decoded jwt.decode(jwt_pre_token,jwt_secret_key,algorithmsES256,audienceappstoreconnect-v1)except Exception as e:print([error], decode exception %s % (e))else:exp datetime.datetime.fromtimestamp(decoded[exp])if exp - datetime.timedelta(seconds60) now:print(jwt token 已过期重新生成)else:print(jwt token 有效使用之前token)return jwt_pre_token# 设置 JWT 的 payloadjwt_payload {iss: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx,iat: int(now.timestamp()),exp: int(expires.timestamp()),aud: appstoreconnect-v1,scope: [GET /notary/v2/submissions,POST /notary/v2/submissions,]}print([info], jwt_header %s % (jwt_header), jwt_payload %s % jwt_payload)token jwt.encode(jwt_payload,jwt_secret_key,algorithmES256,headersjwt_header,)# 打开文件如果文件不存在则创建文件with open(./jwt_token, w) as f:# 将 token 写入文件f.write(token)print([info], jwt token is %s % (token))return token
3. 公证上传
公证处理的流程如下
POST https://appstoreconnect.apple.com/notary/v2/submissions 请求submission s3 上传凭证使用 boto3 框架根据获取的凭证信息查询一次公证状态如果非 Accepted 状态进行上传文件。阻塞式
file_md5None
def post_submissison(filepath): global file_md5body get_body(filepath)token get_jwt_token()file_md5 get_md5(filepath)# 指定文件夹路径folder_path ./output# 缓存路径cache_path f{folder_path}/{file_md5}# 检查文件夹是否存在if not os.path.exists(folder_path):# 如果文件夹不存在则创建文件夹os.makedirs(folder_path)else:# 如果文件夹已经存在则进行相应的处理print([info], %s 已经存在 % folder_path)# 检查文件是否存在if os.path.exists(cache_path):# 读取with open(cache_path, rb) as f:string f.read().decode()output json.loads(string)print([info], 使用上次 submission s3 上传凭证 %s % (output))else:resp requests.post(https://appstoreconnect.apple.com/notary/v2/submissions, jsonbody, headers{Authorization: Bearer token})resp.raise_for_status()output resp.json()print([info], 获取 submission s3上传凭证 %s % (output))# 打开文件如果文件不存在则创建文件with open(cache_path, w) as f:# 将 resp 写入文件f.write(resp.content.decode())# 读取 output 中的内容aws_info output[data][attributes]bucket aws_info[bucket]key aws_info[object]# sub_id output[data][id]# 如果已经完成了公证state get_submission_state(filepath, True)if state True:print([info], file %s alreay finished notarization % (filepath))staple_pkg(filepath)exit(0)s3 boto3.client(s3,aws_access_key_idaws_info[awsAccessKeyId],aws_secret_access_keyaws_info[awsSecretAccessKey],aws_session_tokenaws_info[awsSessionToken],configConfig(s3{use_accelerate_endpoint: True}))print([info], start upload files ...... please wait 2-15 mins)# 上传文件s3.upload_file(filepath, bucket, key)print([info], upload file complete ...)
查询公证状态Accepted 、In Progress、Invalid 目前探测到这三种状态
def get_submission_state(filepath, onceFalse):print([info], get_submission_state %s %s % (filepath, once))global file_md5if not file_md5:file_md5 get_md5(filepath)# 指定文件夹路径folder_path ./output# 缓存路径cache_path f{folder_path}/{file_md5}# 检查文件是否存在if os.path.exists(cache_path):# 获取文件大小file_size os.path.getsize(cache_path)if file_size 0:# 文件内容为空print([info], %s 内容为空未获取到submission信息 % (filepath))return Falseelse:# 读取缓存内容with open(cache_path, rb) as f:string f.read().decode()output json.loads(string)else:return Falsesub_id output[data][id]url fhttps://appstoreconnect.apple.com/notary/v2/submissions/{sub_id}ret Falsewhile True:try:# 获取submissiontoken get_jwt_token()resp requests.get(url, headers{Authorization: Bearer token})resp.raise_for_status()except Exception as e:# 异常处理print([Error], %s get status failed, code %s % filepath % resp.status_code)return Falseelse:# 200 正常返回处理# 检查 statusresp_json resp.json()print([info], GET %s resp is %s , header is %s % (url,resp_json,resp.headers))status resp_json[data][attributes][status]if status Accepted:print([info], %s notarization succesfull % filepath)ret Truebreakif status Invalid:print([info], %s notarization failed % filepath)ret Falsebreakif once False:# 暂停 30 秒time.sleep(30)else:print([info], get_submission_state run once)breakif once False:print_submission_logs(sub_id)return ret
获取日志内容
def print_submission_logs(identifier):try:url fhttps://appstoreconnect.apple.com/notary/v2/submissions/{identifier}/logstoken get_jwt_token()resp requests.get(url, headers{Authorization: Bearer token})resp.raise_for_status()except Exception as e:print([Error], /notary/v2/submissions/%s/logs failed, code %s % (identifier, resp.status_code))else:resp_json resp.json()print([info], notarization %s logs is %s % (identifier, resp_json))
如果 步骤3 查询到结果为 Accepted则使用 stapler 工具打上票据进行分发
def staple_pkg(filepath):global file_md5if not file_md5:file_md5 get_md5(filepath)# 完成公证subprocess.run([xcrun, stapler, staple, filepath])now datetime.datetime.now()# 验证公证结果temp_output_file f./temp_file_{file_md5}with open(temp_output_file, w) as f:subprocess.run([xcrun, stapler, validate, filepath], stdoutf, stderrsubprocess.STDOUT)# 读取验证结果with open(temp_output_file, r) as f:validate_result f.read()os.remove(temp_output_file)# 检查验证结果if The validate action worked! not in validate_result:print([error],\033[31m[error] stapler validate invalid, may be notarization failed!\033[0m)return Falseelse:print([info],staple_pkg succesfull)return True 4. 脚本使用方式
脚本文件 https://github.com/CaicaiNo/Apple-Mac-Notarized-script/blob/master/notarize-web/notarize.py 在执行下列步骤前请先阅读 Generating Tokens for API Requests | Apple Developer Documentation
替换你的秘钥文件 (例如 AuthKey_2X9R4HXF34.p8)
private_key f./../../res/AuthKey_2X9R4HXF34.p8
设置你的 kid # 设置 JWT 的 headerjwt_header {alg: ES256,kid: 2X9R4HXF34,typ: JWT}
设置你的 iss
# 设置 JWT 的 payloadjwt_payload {iss: 57246542-96fe-1a63-e053-0824d011072a,iat: int(now.timestamp()),exp: int(expires.timestamp()),aud: appstoreconnect-v1,scope: [GET /notary/v2/submissions,POST /notary/v2/submissions,]}
调用脚本
python3 -u ./notarize.py --pkg ./Output/${PACKAGE_NAME}_$TIME_INDEX.pkg --private-key ./../../res/AuthKey_2X9R4HXF34.p8if [ $? -eq 0 ]; thenecho ./Output/aTrustInstaller_$TIME_INDEX.pkg notarization successful// 公证成功
else// 公证失败echo ./Output/aTrustInstaller_$TIME_INDEX.pkg notarization failedexit 1
fi