行业背景
由于苹果appstore在中国市场审核的日益严格,很多诸如棋牌类游戏,金融APP都面临无法上架的问题。在这种情况下衍生了以签名为生的产业链。我们简单列举一下各个签名方式的优缺点。
企业签名:
苹果企业帐号(非公司帐号)提供了一种INHOUSE的签名,这种签名方式允许在企业内部分发。
优点:
- 价格适中;
- 不限安装数量;
缺点:
由于企业帐号的申请难度极大,目前市场上独立的企业帐号都被炒到了几十万。 而市场上提供的签名多为共享的企业证书,掉签概率极大。
基本2-3天掉一次,十分影响使用。 现在价格也涨了 大约 2000/包/月
超级签名
超级签名的名字听上去那么高大上,实际上原理上只是将苹果的ADHOC签名自动化了而已,实际分发出来的包还是内部测试的包。
优点:
- 签名稳定,不易掉签
缺点:
单个苹果开发者帐号申请的价格是99美元/年,只有100个内测设备,所以最低成本就是7元/人
如果不是自建的话,外部市场的单价一般是16-20元。
所以超级签名的一个最大的缺点就是成本昂贵。
TF签名
TF(Testflight)签名其实算不上签名了,Testflight本身就是苹果的一种测试机制。 所以当市面上有人拿这个做文章其实有些欺负那些不懂技术的。
优点:
- 成本最低
- 不会掉签
- 安装简单
- 更新会推送
缺点:
- 包体需要进行审核(只不过审核的力度会少很多),但是有微信、支付宝支付的或者赌博软件的审核还会被拒;
- 软件包有测试周期,需要90天更新一次。
- 安装量10000上限
使用前准备
硬件环境:
MAC服务器(1台及以上)带宽良好
Linux服务器(1台及以上)
软件环境:
WEB(分发系统逻辑)
FastLane (自动化更新描述文件和签名)
Jenkins (管理节点及自动化任务)
Rsync (多台服务器之间文件共享)
CDN(文件存储与分发)
内网穿透工具
HTTPS证书
技术架构
大致流程:
基于配置描述文件获取设备的UDID
提交新增开发设备
更新新的profiles并且返回分发平台
分发签名后的ipa包
用户下载并且安装
实现步骤
1.获取设备UUID
1.1 生成.mobileconfig
IOS官方提供了一种基于mobileconfig
来获取UUID的方案,具体可参考文档
.mobileconfig
参考样例
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PayloadContent</key>
<dict>
<key>URL</key>
<string>https://www.jason-z.com/getuuid.mobileconfig</string>
<key>DeviceAttributes</key>
<array>
<string>UDID</string>
<string>IMEI</string>
<string>ICCID</string>
<string>VERSION</string>
<string>PRODUCT</string>
</array>
</dict>
<key>PayloadOrganization</key>
<string>Jason.z</string><!--组织名称-->
<key>PayloadDisplayName</key>
<string>AppFree</string>
<key>PayloadVersion</key>
<integer>1</integer>
<key>PayloadUUID</key>
<string>9CF421B3-9853-4454-BC8A-982CBD3C907C</string><!--自己随机填写的唯一字符串,http://www.guidgen.com/ 可以生成-->
<key>PayloadIdentifier</key>
<string>com.jason-z.profile-service</string>
<key>PayloadDescription</key>
<string>This temporary profile will be used to find and display your current device's UDID.</string>
<key>PayloadType</key>
<string>Profile Service</string>
</dict>
</plist>
注意:IOS12之后由于ATS的限制,所以这里URL必须使用HTTPS地址
当然在实际应用中的话,URL地址可能需要传参,所以这份文件需要动态生成。
动态生成设置Content-type
,样例代码(PHP)
header('Content-type: application/x-apple-aspen-config; chatset=utf-8');
header('Content-Disposition: attachment; filename="company.mobileconfig"');
echo $mobileconfig;
1.2 .mobileconfig
签名
签名前:
生成的.mobileconfig
默认是没有签名的,有多种方式可以签名,我推荐使用HTTPS证书进行签名
openssl smime -sign -in unsigned.mobileconfig -out signed.mobileconfig -signer server.crt -inkey server.key -certfile ca.crt -outform der -nodetach
签名后:
1.3 .mobileconfig
安装
通过JS代码跳转到.mobileconfig
文件URL即可,只不过为了优化体验,可以让用户在安装完.mobileconfig
文件,直接能够跳转到设置
页面,具体代码如下:
var url = 'https://www.jason-z.com/install.mobileconfig';
setTimeout(function () {
window.location.href = url;
}, 1000);
var url2= 'https://www.jason-z.com/jump.mobileprovision';
setTimeout(function () {
window.location.href = url2;
}, 3000);
1.4 UDID
解析
如果前面的步骤顺利的话,当用户安装完描述文件之后,默认就会发送UDID的相关数据给1.1中的URL。
返回的数据的大致格式如下
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IMEI</key>
<string>12 123456 123456 7</string>
<key>PRODUCT</key>
<string>iPhone8,1</string>
<key>UDID</key>
<string>b59769e6c28b73b1195009d4b21cXXXXXXXXXXXX</string>
<key>VERSION</key>
<string>15B206</string>
</dict>
</plist>
参考解析代码(PHP)
<?php
$data = file_get_contents('php://input');
$plistBegin = '<?xml version="1.0"';
$plistEnd = '</plist>';
$pos1 = strpos($data, $plistBegin);
$pos2 = strpos($data, $plistEnd);
$data2 = substr ($data,$pos1,$pos2-$pos1);
$xml = xml_parser_create();
xml_parse_into_struct($xml, $data2, $vs);
xml_parser_free($xml);
$UDID = "";
$IMEI = "";
$PRODUCT = "";
$VERSION = "";
$iterator = 0;
$arrayCleaned = array();
foreach($vs as $v){
if($v['level'] == 3 && $v['type'] == 'complete'){
$arrayCleaned[]= $v;
}
$iterator++;
}
$data = "";
$iterator = 0;
foreach($arrayCleaned as $elem){
$data .= "\n==".$elem['tag']." -> ".$elem['value']."<br/>";
switch ($elem['value']) {
case "IMEI":
$IMEI = $arrayCleaned[$iterator+1]['value'];
break;
case "PRODUCT":
$PRODUCT = $arrayCleaned[$iterator+1]['value'];
break;
case "UDID":
$UDID = $arrayCleaned[$iterator+1]['value'];
break;
case "VERSION":
$VERSION = $arrayCleaned[$iterator+1]['value'];
break;
$iterator++;
}
$params = "UDID=".$UDID."&IMEI=".$IMEI."&PRODUCT=".$PRODUCT."&VERSION =".$VERSION;
header('HTTP/1.1 301 Moved Permanently');
header("Location: https://www.jason-z.com/download.php?".$params);
?>
这里需要注意几点:
- 传回的数据是XML的数据流;
- 接收到数据之后,一定要在最后返回301状态;
- 某些字段可能在旧设备上无法取到。
2.重签名
获取到UDID
之后,我们就需要把我们UDID
添加到我们对应的帐号里,这里我们需要借助 fastlane
这一强大工具来实现。
2.1 安装fastlane
安装fastlane
前需要安装ruby
和xcode
安装RVM
curl -sSL https://get.rvm.io | bash -s stable
安装ruby
rvm install 2.6 --default
安装xcode
组件
xcode-select --install
安装fastlane
sudo gem install fastlane -NV
brew cask install fastlane
2.1 fastlane
自动化流程
配合fastlane的各种工具完成自动化
2.1.1 register_device
注册设备
添加UDID到当前账户
fastlane run register_device udid:"$UDID" name:"$UDID"
2.1.2 produce
注册新的appid
注册新的appid到当前账户
fastlane run produce skip_itc:true app_identifier:"com.jasonz.testipa" app_name:"testipa"
2.1.4 get_provisioning_profile
获取描述文件
重新生成并获取最新的描述文件
fastlane run get_provisioning_profile adhoc:true force:true filename:"new.mobileprovision" skip_install:true app_identifier:"com.jasonz.testipa"
2.1.5 resign
重签名
使用新的证书和描述文件来重签名
fastlane sigh resign xx.ipa --signing_identity "iPhone Distribution: Growu Co., Ltd (KETKXZYNC4)" --provisioning_profile new.mobileprovision
2.1.6 确认签名信息
解压IPA文件,进入到payload/xxx.app目录,执行以下命令
security cms -D -i embedded.mobileprovision
当然,这其中还会遇到一个问题 就是双重认证的问题,目前默认是
参考脚本(SHELL)
#!/bin/sh -ilex
# 设置环境变量
export FASTLANE_USER="xxx"
export FASTLANE_PASSWORD="xxx"
export FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD="ivud-ynqx-wgvf-gxim"
export FASTLANE_SESSION="xxx"
# 双重认证
# export SPACESHIP_2FA_SMS_DEFAULT_PHONE_NUMBER="+86 xxxx"
# fastlane spaceauth -u 'xxxxe'
# 注册设备
# fastlane run register_device udid:"xxx" name:'123'
# 注册appid
fastlane run produce skip_itc:true app_identifier:"xxx" app_name:"xxxDEMO"
# 获取描述文件
# fastlane run get_provisioning_profile adhoc:true filename:"xxxx.mobileprovision" skip_install:true app_identifier:"xxxx"
# 重签名
# fastlane sigh resign /Users/tuo3/ipa/VLionAdSDKDemo.ipa --signing_identity "iPhone Distribution:xxxx" --provisioning_profile xxx.mobileprovision
# 上传到CDN
# 发送通知
3.OTA分发
生成了新的IPA包,下一步就是分发了,如果是借助于类似FIR,蒲公英之类的平台,他们一般会帮你搭建好OTA服务。如果你是用第三方的分发,本章节可以略过。
3.1 OTA是什么
OTA
即 Over-the-Air,是 Apple 在 iOS4 中新加的一项技术,目的是让开发者能够脱离 Appstore,实现从服务器下载并安装 iOS 应用。
用户只需要在 iOS 设备的浏览器中,打开itms-services://协议链接,就可以直接安装App。(注意:此处的安装是指,个人账号需要注册设备,企业账号无需注册设备)
**3.2 OTA协议
这里必须注意生成的pLIST中的URL以及PLIST内容里面的IPA和ICON均需要使用HTTPS。
样例
itms-services://?action=download-manifest&url=https://www.example.com/apps/download/mainifest.plist
这里遇到一个小坑,就是协议中的URL必须要urlencode.
3.3 生成PLIST
PLIST文件一般来说是需要我们来构造的,构造的主要字段如下:
plist生成的Content-Type
需要为:
header('Content-Type: text/xml');
或者
header('Content-Type: application/xml');
Plist样例
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>items</key>
<array>
<dict>
<key>assets</key>
<array>
<dict>
<key>kind</key>
<string>software-package</string>
<key>url</key>
<string>https://www.example.com/apps/app_name.ipa</string>
</dict>
<dict>
<key>kind</key>
<string>display-image</string>
<key>url</key>
<string>https://www.example.com/image.57×57.png</string>
</dict>
<dict>
<key>kind</key>
<string>full-size-image</string>
<key>url</key>
<string>https://www.example.com/image.512×512.png</string>
</dict>
</array>
<key>metadata</key>
<dict>
<key>bundle-identifier</key>
<string>com.domainname.appname</string>
<key>bundle-version</key>
<string>1.0.0</string>
<key>kind</key>
<string>software</string>
<key>title</key>
<string>app_name</string>
</dict>
</dict>
</array>
</dict>
</plist>
4、Jenkins
4.1 安装Jenkins
官方文档:https://jenkins.io/zh/doc/book/installing
以Debian/Ubuntu为例
wget -q -O - https://pkg.jenkins.io/debian/jenkins.io.key | sudo apt-key add -
sudo sh -c 'echo deb http://pkg.jenkins.io/debian-stable binary/ > /etc/apt/sources.list.d/jenkins.list'
sudo apt-get update
sudo apt-get install jenkins
4.2 添加Mac从节点
目前来说,我们的Mac服务器都是放在公司内网的,如果需要外部服务器访问,必须使用内网穿透将Mac服务器映射出去。
市面上流行的穿透工具:
- Ngrok简单免费/付费(几元每月)
- Natapp简单免费/付费(几元每月)
- 花生壳简单付费(偏贵)
- Ssh、autossh难免费
- Frp难付费
- Lanproxy难付费
- Spike难付费
本文以Natapp为例。
4.2.1 注册Natapp
到https://natapp.cn/ 注册账号(邀请码:5864D864 9折优惠)
4.2.2 创建隧道
免费隧道 会不停地更换端口,只适合测试,正式环境最好使用付费隧道
隧道协议TCP, 本地端口22
创建成功后,会获取到authToken供客户端使用。
4.2.3 客户端配置
到https://natapp.cn/#download 下载MAC客户端,放置到/usr/local/natapp,启动
./natapp -authtoken=xxxxx
后台运行
nohup ./natapp -authtoken=xxxx -log=stdout &
运行成功截图:
就代表映射成功 http://test.s3.natapp.cc-> 127.0.0.1:8080
4.2.4 MAC服务配置
Mac需要开启远程登录
4.2.5 创建节点
选择固定节点,并且配置主机地址为4.2.3中获取到的连接地址和对应端口。
如果配置成功的话,就可以启动代理了。
4.3 创建任务
创建一个任务,因为我们只是为了执行编译脚本,所以直接选择"自由风格"的即可。
在构建里面输入脚本即可
4.4 远程调用
如果我们的分发系统和MAC机器不在同一台电脑上的话,就需要我们进行远程调用Jenkins
任务。
Jenkins 提供了一套Remote Access API方便外部调用。
官方文档:https://wiki.jenkins.io/display/JENKINS/Remote+access+API
请求示例:
curl -X POST JENKINS_URL/job/JOB_NAME/buildWithParameters \
--user USER:TOKEN \
--data-urlencode json='{"parameter": [{"name":"id", "value":"123"}, {"name":"verbosity", "value":"high"}]}'
token
获取方式
在账户设置页面添加API-TOKEN
参数请求
一般我们会再远程调用的时候传入我们构建的参数,这里我们需要对任务进行设置。
勾选"This project is parameterized",然后添加具体的参数即可。
最后的话
虽然我基本上披露了所有的技术细节,但是对于不懂技术的或者暂时没有资源的朋友来说,想要完成这套系统来说还是有些困难的。
https://isign.pro 已经正式启用了,提供应用分发、超级签名和TF签名等服务,如有需要可以联系我购买使用(微信号:foxmee).
1581788705.png
全部评论