苹果超级签研究之在linux平台脚本打包

由于某种原因,一些苹果包无法上appstore ,并且企业签也频繁掉签名,因此超级签因需而生

超级签平台有很多,大体技术就是利用苹果开发者账户Ad-Hoc分发通道,把安装设备当做开发设备进行分发。

既然签名用是 Ad-Hoc ,那么 Ad-Hoc 所具有的优劣势也一并继承了下来:

优势:

  1. 直接分发,安装即可运行,不需要用户做企业证书的信任操作

  2. 目前稳定,不会有证书吊销导致的业务风险(后续苹果政策风险非常高)

缺点:

  1. 单开发者账号的iPhone设备数量只有100个,导致分发成本非常高(99美元/1年/100个设备),并且设备无法在使用中删除,只可在会员续费的时候方可移除

  2. 开发者账号需要预先写入安装设备的UDID,在工具链不通的情况下,获取用户的UDID相对困难和繁琐,而且手动写入UDID不存在商用可行性,当然目前这个缺点被解决了,参考https://github.com/shaojiankui/iOS-UDID-Safari 

整体原理

  1. 设备安装描述文件后,会向服务器发送设备的UDID。

  2. 服务器收到UDID后,将UDID注册到某个开发者账号下。

  3. 再生成签名用的描述文件,给IPA签名。

  4. 然后iPA传Server,使用itms-services方式让用户下载。

开源工具链

获取设备UDID的第三方库:

https://github.com/shaojiankui/iOS-UDID-Safari

Apple Developer Center 自动化工具:

https://github.com/fastlane/fastlane/tree/master/spaceship

自动签名封包工具:

https://github.com/apperian/isign

分发平台

fir.im 

在linux平台写了个脚本,用与签名证书

#!/usr/bin/ruby
# -*- coding: UTF-8 -*-

require "spaceship"

class DevelopPortalHandle
   def login(username,password)
      Spaceship::Portal.login(username,password)
   end

    def createCert(file_format_path_name)
        csr, pkey = Spaceship::Portal.certificate.create_certificate_signing_request
        cert=Spaceship::Portal.certificate.production.create!(csr: csr)
        File.write(file_format_path_name+".key",pkey)
        File.write(file_format_path_name+".cer",cert.download_raw)
        File.write(file_format_path_name+".pem",cert.download)
        File.write(file_format_path_name+".info",cert)
    end

    def getDevice(file_format_path_name)
        all_devices= Spaceship::Portal.device.all(include_disabled: true)
        if  0 < all_devices.length then
            File.write(file_format_path_name+".devices.info",all_devices)
         end
    end

   def createApp(appid,appname)
      app = Spaceship::Portal.app.find(appid)
      if !app then
         app = Spaceship::Portal.app.create!(bundle_id: appid, name: appname)
            app.update_service(Spaceship::Portal.app_service.push_notification.on)
            app.update_service(Spaceship::Portal.app_service.vpn_configuration.on)
      end
   end

    def deleteApp(appid)
      app = Spaceship::Portal.app.find(appid)
      if app then
         app.delete!
      end
   end

    #appstore or inHouse
   def createDistributionProvision(provisioningClass,appid,provisionName,certid)
       cert = Spaceship::Portal.certificate.Production.find(id=certid)
       if !cert then
           cert = Spaceship::Portal.certificate.production.all.last
           if !cert then
               cert = Spaceship::Portal.certificate.AppleDistribution.all.last
               if !cert then
                   cert = Spaceship::Portal.certificate.AppleDistribution.find(id=certid)
               end
           end
      end
      profile = provisioningClass.create!(bundle_id: appid,certificate:cert,name:provisionName.split("/")[-1])
        return profile
   end

    #appstore or inHouse
    def downloadDistributionProvision(provisioningClass,appid,provisionName,certid)
        #查找有没有provision文件
        filtered_profiles = provisioningClass.find_by_bundle_id(bundle_id: appid)
        profile = nil
        if  0 < filtered_profiles.length then
            profile = filtered_profiles[0]
            all_devices = Spaceship::Portal.device.all
            profile.devices=all_devices
            profile.update!
            profile = provisioningClass.find_by_bundle_id(bundle_id: appid)[0]
        elsif 0 == filtered_profiles.length then
            profile = createDistributionProvision(provisioningClass,appid,provisionName,certid)
        end

        if profile.status == "Invalid" or profile.status == "Expired"  then
          profile.repair! # yes, that's all you need to repair a profile
        end

        File.write(provisionName, profile.download)
        return provisionName
    end

    def delete_profile(provisioningClass,appid)
        filtered_profiles = provisioningClass.find_by_bundle_id(bundle_id: appid)
        profile = nil
        if  0 < filtered_profiles.length then
            profile = filtered_profiles[0]
            profile.delete!
         end
    end
    def addDevice(device_name,device_udid)
        device = Spaceship::Portal.device.find_by_udid(device_udid, include_disabled: false)
        if !device then
            Spaceship::Portal.device.create!(name: device_name, udid: device_udid)
        end
    end

    def enableDevice(device_udid)
        device=Spaceship::Portal.device.find_by_udid(device_udid, include_disabled: true)
        if device then
            device.enable!
        end
    end

    def disableDevice(device_udid)
        device = Spaceship::Portal.device.find_by_udid(device_udid)
        if device then
            device.disable!
        end
    end

end

    handle = DevelopPortalHandle.new()
    handle.login(ARGV[0],ARGV[1])
    function =  ARGV[2]

    case function
    when "device"
        action = ARGV[3]
        device_udid = ARGV[4]
        device_name = ARGV[5]

        case action
        when "add"
            device_name = device_udid+','+device_udid
            handle.addDevice(device_name,device_udid)
        when "enable"
            handle.enableDevice(device_udid)
        when "disable"
            handle.disableDevice(device_udid)
        when "get"
            handle.getDevice(device_udid)
        end

    when "app"
        action = ARGV[3]
        app_id = ARGV[4]
        app_name = ARGV[5]
      appid = app_id+app_name

        case action
        when "add"
            handle.createApp(appid,app_name)
        when "del"
            handle.deleteApp(appid)
            handle.delete_profile(Spaceship::Portal.provisioning_profile.ad_hoc,appid)
        end
    when "profile"
        action = ARGV[3]
        app_id = ARGV[4]
        app_name = ARGV[5]
        appid = app_id+app_name

        case action
        when "add"
            device_udid = ARGV[6]
            device_name = ARGV[7]
            certid = ARGV[8]
            provisionName = ARGV[9]
            handle.createApp(appid,app_name)
            handle.addDevice(device_name,device_udid)
            provisionPath = handle.downloadDistributionProvision(Spaceship::Portal.provisioning_profile.ad_hoc,appid,provisionName,certid)
        when "del"
            handle.delete_profile(Spaceship::Portal.provisioning_profile.ad_hoc,appid)
        end

    when "cert"
        action = ARGV[3]
        file_format_path_name = ARGV[4]
        case action
        when "add"
            handle.createCert(file_format_path_name)
        end

    when "active"
        puts "active"
    else
        puts "error"
    end

Python调用操作

class AppDeveloperApi(object):
    def __init__(self, username, password, certid):
        self.username = username
        self.password = password
        self.certid = certid
        script_path = os.path.join(SUPER_SIGN_ROOT, 'scripts', 'apple_api.rb')
        self.cmd = "ruby %s '%s' '%s'" % (script_path, self.username, self.password)

    def active(self, user_obj):
        self.cmd = self.cmd + " active "
        logger.info("ios developer active cmd:%s" % (self.cmd))
        result = {}
        try:
            result = pshell_command(self.cmd, user_obj, self.username)
            logger.info("ios developer active cmd:%s  result:%s" % (self.cmd, result))
            if result["exit_code"] == 0:
                return True, result
        except Exception as e:
            logger.error("ios developer active cmd:%s Failed Exception:%s" % (self.cmd, e))
        return False, result

    def file_format_path_name(self, user_obj):
        cert_dir_name = make_app_uuid(user_obj, self.username)
        cert_dir_path = os.path.join(SUPER_SIGN_ROOT, cert_dir_name)
        if not os.path.isdir(cert_dir_path):
            os.makedirs(cert_dir_path)
        return os.path.join(cert_dir_path, cert_dir_name)

    def create_cert(self, user_obj):
        self.cmd = self.cmd + " cert add  '%s'" % (self.file_format_path_name(user_obj))
        return exec_shell(self.cmd)

    def get_profile(self, bundleId, app_id, device_udid, device_name, provisionName):
        self.cmd = self.cmd + " profile add '%s' '%s' '%s' '%s' '%s' '%s'" % (
            bundleId, app_id, device_udid, device_name, self.certid, provisionName)
        return exec_shell(self.cmd)

    def del_profile(self, bundleId, app_id):
        self.cmd = self.cmd + " profile del '%s' '%s'" % (bundleId, app_id)
        result = exec_shell(self.cmd)

    def set_device_status(self, status, device_udid):
        if status == "enable":
            self.cmd = self.cmd + " device enable '%s'" % (device_udid)
        else:
            self.cmd = self.cmd + " device disable '%s'" % (device_udid)
        result = exec_shell(self.cmd)

    def add_device(self, device_udid, device_name):
        self.cmd = self.cmd + " device add '%s' '%s'" % (device_udid, device_name)
        result = exec_shell(self.cmd)

    def get_device(self, user_obj):
        self.cmd = self.cmd + " device get '%s' " % (self.file_format_path_name(user_obj))
        return exec_shell(self.cmd)

    def add_app(self, bundleId, app_id):
        self.cmd = self.cmd + " app add '%s' '%s'" % (bundleId, app_id)
        result = exec_shell(self.cmd)

    def del_app(self, bundleId, app_id):
        self.cmd = self.cmd + " app del '%s' '%s'" % (bundleId, app_id)
        result = exec_shell(self.cmd)


class ResignApp(object):

    def __init__(self, my_local_key, app_dev_pem):
        self.my_local_key = my_local_key
        self.app_dev_pem = app_dev_pem
        # script_path=os.path.join(SUPER_SIGN_ROOT,'scripts','apple_api.rb')
        self.cmd = "isign  -c '%s'  -k '%s' " % (self.app_dev_pem, self.my_local_key)

    @staticmethod
    def sign_mobileconfig(mobilconfig_path, sign_mobilconfig_path, ssl_pem_path, ssl_key_path):
        cmd = "openssl smime -sign -in %s -out %s -signer %s " \
              "-inkey %s -certfile %s -outform der -nodetach " % (
                  mobilconfig_path, sign_mobilconfig_path, ssl_pem_path, ssl_key_path, ssl_pem_path)
        return exec_shell(cmd)

    def sign(self, new_profile, org_ipa, new_ipa):
        self.cmd = self.cmd + " -p '%s' -o '%s'  '%s'" % (new_profile, new_ipa, org_ipa)
        result = exec_shell(self.cmd)

  • 苹果超级签研究之在linux平台脚本打包已关闭评论
  • 46 views
    A+
发布日期:2020年10月28日  所属分类:linux  Python