由于某种原因,一些苹果包无法上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)