动手做一个超级签名

3,626 阅读5分钟

随着苹果对于企业分发证书的频繁掉签和对企业申请要求难度高,代签名行业就利用苹果的ad-hoc这个证书玩意所弄出来的一个替代品,即所谓的超级签名。


从整个安装流程上来看,超级签名是不需要像企业证书一样点击信任,体验上要比企业分发更简单和容易接受。


拿现在市面上某某平台超级签名价格来看:


也就是说现在的超级签名是按照每台设备来收费,最高可达22元一台,这个价格就有点高了,越是这样的价格就让人好奇这个玩意是怎么搞出来的。


超级签名他又是利用了苹果的什么东西搞出来的呢?


首先苹果对开发者他是有一个Ad-Hoc分发通道,把苹果设备当做开发设备进行分发。换句话说,我们在公司让ios的人给你安装开发包,也是这个操作。


怎么说苹果的开发者都要99美刀一年,一年最多给你账号弄100个设备作为开发设备进行安装包,也就没有所谓上appstore 上企业签名 就能直接给你装到手机了,那么他的缺点嘛,也就99美刀一年,并且也只能拥有最多100个开发设备进行安装。


实现这个超级签名他的过程又是怎么样的呢?



如图:

  1. 基于配置描述文件获取设备的UDID

  2. 提交新增开发设备

  3. 更新新的profiles并且返回分发平台

  4. 分发签名后的ipa包

  5. 用户下载并且安装


下面我们就进入实战操作:



基于配置描述文件获取设备的UDID


需要准备一个mobileconfig的xml文件,就像下面一样的文件。

需要准备一个触发下载xxx.mobileconfig的html文件

html的样式如下:



html代码:

<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <title>测试uuid</title>    <style type="text/css">        body {            margin: 0;            padding: 0;            color: #333;            font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;            font-size: 14px;            line-height: 1.42857;        }        #content {            display:flex;            flex-direction: column;            align-items: center;            justify-content: center;            height: 100vh;            text-align: center;        }        .buttons {            background: #333333 none repeat scroll 0 0;            border: 1px solid #777;            color: #fff;            cursor: pointer;            font-family: "Microsoft Yahei", Arial, Tahoma, sans-serif;            font-style: normal;            font-weight: bold;            padding: 20px;            margin-left: 10px;            text-decoration: none;            text-transform: none;            white-space: nowrap;            font-size: 66px;        }</style></head><body><div id="content">    <a class="buttons" href="file/test.mobileconfig" target="_blank">获取UDID</a></div></body></html>


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>http://localhost:8080/receive.php</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>test.udid.com</string>        <key>PayloadDisplayName</key>        <string>测试查询udid</string>        <key>PayloadVersion</key>        <integer>1</integer>        <key>PayloadUUID</key>        <string>3C4DC7D2-E475-3375-489C-0BB8D737A653</string>        <key>PayloadIdentifier</key>        <string>dev.skyfox.profile-service</string>        <key>PayloadDescription</key>        <string>本文件仅用来获取设备ID</string>        <key>PayloadType</key>        <string>Profile Service</string>    </dict></plist>


当用户在safari浏览器中下载了mobileconfig文件,会自动跳转到对应的界面



当用户触发了上图的右上角的安装按钮,苹果会发送一个post请求到你mobileconfig文件中设置的后端服务器地址,我们需要在后端处理一下对应的解析xml数据。就可以获取到当前用户的udid。


php后端代码如下:


class UDID{    /**     * 解析苹果post传递来的xml数据     * @param $data string 数据源     * @return array     */    public function parseAppleData($data){        $udidData = [];        $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, $udidData);        xml_parser_free($xml);        return $udidData;    }    public function createQueryData($data){        $udidData = $this->parseAppleData($data);        list ($iterator, $arrayCleaned, $query) = [            0,            [],            []        ];        foreach ($udidData as $v) {            if ($v['level'] == 3 && $v['type'] == 'complete') {                $arrayCleaned[] = $v;            }            $iterator++;        }        $iterator = 0;        foreach ($arrayCleaned as $elem) {            switch ($elem['value']) {                case "CHALLENGE":                    $query['CHALLENGE'] = $arrayCleaned[$iterator + 1]['value'];                    break;                case "DEVICE_NAME":                    $query['DEVICE_NAME'] = $arrayCleaned[$iterator + 1]['value'];                    break;                case "PRODUCT":                    $query['DEVICE_PRODUCT'] = $arrayCleaned[$iterator + 1]['value'];                    break;                case "UDID":                    $query['UDID'] = $arrayCleaned[$iterator + 1]['value'];                    break;                case "VERSION":                    $query['DEVICE_VERSION'] = $arrayCleaned[$iterator + 1]['value'];                    break;            }            $iterator++;        }        return $query;    }}$query = (new UDID)->createQueryData(file_get_contents('php://input'));// 写文件查看数据源file_put_contents('./test.log',json_encode($query));


最终结果在文件可以看到这样数据:


{"DEVICE_PRODUCT":"iPhone7,2","UDID":"18314ad381ff54b1862905e1253006e700000000","DEVICE_VERSION":"14E8301"}


提交新增开发设备 更新profile文件



接下来的关键点就是如何在获取到用户的UDID之后,秒级完成注册新的开发者设备+更新Provisioning Profile的。这里我们需要借助开源工具(Spaceship):




开源Ruby库地址: https://github.com/fastlane/fastlane/tree/master/spaceship


安装脚本:


sudo gem install fastlane


ruby实例代码:


require 'spaceship'if !ARGV[0] || !ARGV[1]    puts "请检查输入的账户密码是否完整  \n"    exitendif !ARGV[2]    puts "请输入苹果用户的udid  \n"    exitendif !ARGV[3]    puts "请输入苹果用户的标示名称  \n"    exitend# 创建设备(根据需要在进行操作这个)def create_device(name, udid)    Spaceship::Portal.device.create!(name: name, udid: udid)end# 写入设备号def write_devices    puts "开始获取证书  \n"    p = Spaceship::Portal.provisioning_profile.development.all    puts "开始加入新的设备号  \n"    # 拿到所有device加入设备号 如果是指定的话需要自己手动创建数据源进行修改    all_devices = Spaceship::Portal.device.all    p.each do |profile|        profile.devices = all_devices        profile.update!        puts "更新完成"    end    puts "重新获取最新的证书  \n"    # 重新刷新上面的证书,苹果这里会更新标示号    p = Spaceship::Portal.provisioning_profile.development.all    puts "执行下载profile文件"    # 文件名字    File.write("./embedded.mobileprovision", p.first.download)    puts "profile文件下载成功  \n"end# 下载对应证书这里可以做一个缓存 不是需要每一次都下载证书的def download_cer    puts "获取证书"    prod_certs = Spaceship::Portal.certificate.development.all    cert_content = prod_certs.first.download    File.write("./ios_development.cer", cert_content)    puts "证书文件下载成功 \n"endbegin    # 登陆    Spaceship::Portal.login(ARGV[0], ARGV[1])    write_devices    download_cer    exit(1)rescue => exception    printf(exception.message)    exit(0)end


使用方法:


ruby xxx.rb 苹果帐号 苹果帐号密码 udid 别名

在使用这个库的login方法的时候要特别注意的问题就是苹果双重身份验证的问题,我建议的解决方案就是首先进行一下命令框的操作这样就可以拿到一个月时间的身份认证



输入对应帐号密码以及设备验证号即可,如果你不使用设备码来授权的,可以使用sms短信验证码,以mac为例子需要在~/.bash_profile下加入,国内一定要用+86开头。


export SPACESHIP_2FA_SMS_DEFAULT_PHONE_NUMBER=+861234567890


进行对应的ios包进行重签



对于ios的重新签名有很多方法,比如使用库的操作 使用xcode命令 使用自己编写的脚本等等


我们这里推荐使用的是sign这个框架来解决这个问题



库地址: https://github.com/fastlane/fastlane/tree/master/sigh


终端使用:


fastlane sigh resign

该命令是一种步骤操作,让你选择ipa包 证书编码等等即可完成


如果你觉得你需要自动化可以参考这个命令的参数进行修改调用


fastlane sigh resign ./path/app.ipa --signing_identity "iPhone Distribution: Felix Krause" -p "my.mobileprovision"


用户下载并且安装



当以上的命令都完成的时候可以进行输出回php脚本,进行下载刚才重新签名好的包进行分发下载


header('HTTP/1.1 301 Moved Permanently');header("Location: http://xxxxx/download.php?".$params);


结语


通过开源社区的力量,我们成功整个机制上的关键技术点。我们需要感谢的是spaceship这个库的作者,他把苹果的api全部暴露出来,使用ad-hoc这种模式的分开也就存在了可能。


由于使用了ad-hoc作为商业分发,分发平台以这种情况绕过苹果的审核是严重违反 APPLE 开发商计划许可协议的3.3.3条款:


未经 Apple 预先书面核准或依照第 3.3.25 条 (In-App Purchase API) 得到允许,应用程序不可经由 App Store、Custom App Distribution 或 TestFlight 以外的分销渠道提供、解锁或激活附加的特征或功能


但是对于那些黑产业来说,也就无视这些条款的存在。但是对于技术而言,我们是需要将它使用到正途上的。


加入我们一起学习吧!每天都会更新文章到各大平台以及公众号。

路漫漫其修远兮,吾将上下而求索。