由于某种原因,一些苹果包无法上appstore ,并且企业签也频繁掉签名,因此超级签因需而生
超级签平台有很多,大体技术就是利用苹果开发者账户Ad-Hoc分发通道,把安装设备当做开发设备进行分发。
既然签名用是 Ad-Hoc ,那么 Ad-Hoc 所具有的优劣势也一并继承了下来:
优势:
-
直接分发,安装即可运行,不需要用户做企业证书的信任操作
-
目前稳定,不会有证书吊销导致的业务风险(后续苹果政策风险非常高)
缺点:
-
单开发者账号的iPhone设备数量只有100个,导致分发成本非常高(99美元/1年/100个设备),并且设备无法在使用中删除,只可在会员续费的时候方可移除
-
开发者账号需要预先写入安装设备的UDID,在工具链不通的情况下,获取用户的UDID相对困难和繁琐,而且手动写入UDID不存在商用可行性,当然目前这个缺点被解决了,参考https://github.com/shaojiankui/iOS-UDID-Safari
整体原理
-
设备安装描述文件后,会向服务器发送设备的UDID。
-
服务器收到UDID后,将UDID注册到某个开发者账号下。
-
再生成签名用的描述文件,给IPA签名。
-
然后iPA传Server,使用itms-services方式让用户下载。
开源工具链
获取设备UDID的第三方库:
Apple Developer Center 自动化工具:
自动签名封包工具:
分发平台:
在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)