行业背景

由于苹果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证书

技术架构

file

大致流程:

基于配置描述文件获取设备的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 签名

签名前:

file

生成的.mobileconfig默认是没有签名的,有多种方式可以签名,我推荐使用HTTPS证书进行签名

openssl smime -sign -in unsigned.mobileconfig -out signed.mobileconfig -signer server.crt -inkey server.key  -certfile ca.crt -outform der -nodetach

签名后:

file

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 前需要安装rubyxcode

安装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

file

当然,这其中还会遇到一个问题 就是双重认证的问题,目前默认是

参考脚本(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服务。如果你是用第三方的分发,本章节可以略过。

file

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文件一般来说是需要我们来构造的,构造的主要字段如下:

file

file

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

file

创建成功后,会获取到authToken供客户端使用。

4.2.3 客户端配置

到https://natapp.cn/#download 下载MAC客户端,放置到/usr/local/natapp,启动

./natapp -authtoken=xxxxx

后台运行

nohup ./natapp -authtoken=xxxx -log=stdout &

运行成功截图:

file

就代表映射成功 http://test.s3.natapp.cc-> 127.0.0.1:8080

4.2.4 MAC服务配置

Mac需要开启远程登录

file

4.2.5 创建节点

选择固定节点,并且配置主机地址为4.2.3中获取到的连接地址和对应端口。

file

file

如果配置成功的话,就可以启动代理了。

file

4.3 创建任务

创建一个任务,因为我们只是为了执行编译脚本,所以直接选择"自由风格"的即可。

在构建里面输入脚本即可

file

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

file

参数请求

一般我们会再远程调用的时候传入我们构建的参数,这里我们需要对任务进行设置。

勾选"This project is parameterized",然后添加具体的参数即可。

file

最后的话

虽然我基本上披露了所有的技术细节,但是对于不懂技术的或者暂时没有资源的朋友来说,想要完成这套系统来说还是有些困难的。

https://isign.pro 已经正式启用了,提供应用分发超级签名TF签名等服务,如有需要可以联系我购买使用(微信号:foxmee).

1581788705.pngfile