go实现iOS重签名模块(iOS超级签名、蒲公英ios内测分发原理)

Related tags

isign super-signature
Overview

README.md

扫码_搜索联合传播样式-标准色版.png

这是什么

一个用go实现的iOS重签名模块,即市面上的iOS超级签名、蒲公英ios内测分发原理

使用本模块可以进行基本的IPA安装包重签名分发

实现功能:苹果开发者账号管理、IPA安装包管理

前提

1.生成ios.csr和ios.key文件

openssl genrsa -out ios.key 2048
openssl req -new -sha256 -key ios.key -out ios.csr

2.需要https,自行配置ssl证书

手动部署

git clone https://github.com/togettoyou/super-signature.git
# 进入项目isign目录下
cd super-signature/isign
# 更改Python默认编码
cp ./sitecustomize.py /usr/lib/python2.7/sitecustomize.py
# 验证(输出utf-8即更改成功)
python -c "import sys; print sys.getdefaultencoding()"
# 安装isign
tar xvf isign.tar.gz
cd isign
./version.sh
python setup.py build
python setup.py install
# 验证
isign -h
# 开启go mod
go env -w GO111MODULE=on
# 回到项目目录(记得更改app.ini配置信息)
cd super-signature
go run main.go
# 浏览器访问 http://localhost:10016/swagger/index.html

使用docker部署

docker-compose up

详见Dockerfiledocker-compose.yml文件

原理

语雀浏览

基本流程

  1. 添加Apple开发者账号(绑定App Store Connect API)
  2. 根据描述文件获得用户设备的UDID
  3. 借助App Store Connect API在开发者中心添加UDID、创建证书等
  4. 重签名(使用isign开源项目实现在linux服务器上重签名)
  5. 将ipa包上传到服务器上,配置itms-service服务来做分发

API

image.png

/api/v1/getAllPackage 返回数据格式

{
  "code": 0,
  "msg": "成功",
  "data": [
    {
      "ID": 1,
      "IconLink": "应用图标地址",
      "BundleIdentifier": "应用包名",
      "Name": "应用名称",
      "Version": "应用版本号",
      "BuildVersion": "应用BuildVersion",
      "MiniVersion": "最低支持ios版本",
      "Summary": "简介",
      "AppLink": "应用下载地址,iPhone使用Safari浏览器访问即可下载",
      "Size": "应用大小",
      "Count": "累计下载量"
    }
  ]
}

界面截图

image.png image.png

步骤:

1. 添加Apple开发者账号

API接口文档:https://developer.apple.com/documentation/appstoreconnectapi

使用App Store Connect API需要到https://appstoreconnect.apple.com/access/api 生成API密钥P8文件, 以及对应的密钥ID和账号的Issuer ID。 image.png

首先创建一个结构体存放p8(下载的API密钥文件内容),kid (密钥ID),Iss (Issuer ID)

type Authorize struct {
	P8  string
	Iss string
	Kid string
}

使用fasthttp来发送http请求

func (a Authorize) httpRequest(method string, url string, body []byte) (*fasthttp.Response, error) {
	token, err := a.createToken()
	if err != nil {
		return nil, err
	}
	req := fasthttp.AcquireRequest()
	resp := fasthttp.AcquireResponse()
	defer fasthttp.ReleaseRequest(req)
	req.Header.SetContentType("application/json")
	req.Header.Set("Authorization", fmt.Sprintf("Bearer %v", token))
	req.Header.SetMethod(method)
	req.SetRequestURI(url)
	req.SetBody(body)
	if err := fasthttp.Do(req, resp); err != nil {
		return nil, err
	}
	return resp, nil
}

token验证关键代码

func (a Authorize) createToken() (string, error) {
	token := &jwt.Token{
		Header: map[string]interface{}{
			"alg": "ES256",
			"kid": a.Kid,
		},
		Claims: jwt.MapClaims{
			"iss": a.Iss,
			"exp": time.Now().Add(time.Second * 60 * 5).Unix(),
			"aud": "appstoreconnect-v1",
		},
		Method: jwt.SigningMethodES256,
	}
	privateKey, err := authKeyFromBytes([]byte(a.P8))
	if err != nil {
		return "", err
	}
	return token.SignedString(privateKey)
}

func authKeyFromBytes(key []byte) (*ecdsa.PrivateKey, error) {
	var err error
	// Parse PEM block
	var block *pem.Block
	if block, _ = pem.Decode(key); block == nil {
		return nil, errors.New("token: AuthKey must be a valid .p8 PEM file")
	}
	// Parse the key
	var parsedKey interface{}
	if parsedKey, err = x509.ParsePKCS8PrivateKey(block.Bytes); err != nil {
		return nil, err
	}
	var pkey *ecdsa.PrivateKey
	var ok bool
	if pkey, ok = parsedKey.(*ecdsa.PrivateKey); !ok {
		return nil, errors.New("token: AuthKey must be of type ecdsa.PrivateKey")
	}
	return pkey, nil
}

调用

resp, err := authorize.httpRequest("GET", "https://api.appstoreconnect.apple.com/v1/devices", nil)
defer fasthttp.ReleaseResponse(resp)
if err != nil {
	return err
}

这样就可以直接借助App Store Connect API来完成添加udid、创建Certificates证书、创建BundleIds、创建Profile等来实现超级签名的核心功能。

2. 获取UDID

创建udid.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>PayloadContentkey>
        <dict>
            <key>URLkey>
            <string>https://qq.comstring> //回调接收UDID等信息的,借用这个回调地址将udid传到服务器后台
            <key>DeviceAttributeskey>
            <array>
                <string>UDIDstring>
                <string>IMEIstring>
                <string>ICCIDstring>
                <string>VERSIONstring>
                <string>PRODUCTstring>
            array>
        dict>
        <key>PayloadOrganizationkey>
        <string>仅用于查询设备UDID安装APPstring>
        <key>PayloadDisplayNamekey>
        <string>仅用于查询设备UDID安装APPstring>
        <key>PayloadVersionkey>
        <integer>1integer>
        <key>PayloadUUIDkey>
        <string>62b2dc10-72be-4022-bdec-f1ea3c720d1astring> //可在https://www.guidgen.com/随机生成
        <key>PayloadIdentifierkey>
        <string>com.yy.UDID-serverstring>
        <key>PayloadDescriptionkey>
        <string>仅用于查询设备UDID安装APPstring>
        <key>PayloadTypekey>
        <string>Profile Servicestring>
    dict>
plist>

iPhone使用Safari浏览器访问放在服务器上的mobileconfig文件,进行安装描述文件,安装完成后苹果会回调我们设置的url,就可以得到udid信息。设置的url是一个post接口,接收到udid信息处理完逻辑后,301重定向到我们需要跳转的网站,如果不301重定向,iPhone会显示安装失败!

image.png

解析苹果返回的Plist信息,提取UDID

DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
  <dict>
    <key>IMEIkey>
    <string>12 345678 901234 566789string>
    <key>PRODUCTkey>
    <string>iPhone10,3string>
    <key>UDIDkey>
    <string>abcd0123456789XXXXXXXXXXXXstring>
    <key>VERSIONkey>
    <string>12345string>
  dict>
plist>

只需要解析出udid,调用App Store Connect API将UDID添加到苹果开发者中心即可。

3. 重签名

添加开发者账号之前在本地使用openssl生成后续所需要的key和csr文件

openssl genrsa -out ios.key 2048
openssl req -new -sha256 -key ios.key -out ios.csr

利用csr文件调用CreateCertificates (App Store Connect API) 可以生成cer 证书

接着利用cer证书生成pem文件(公钥)

openssl x509 -in ios_development.cer -inform DER -outform PEM -out ios_development.pem

公钥ios_development.pem、私钥ios.key、描述文件mobileprovision(调用CreateProfile App Store Connect API)、原始ipa 四大材料已凑齐!

使用开源项目isign实现在Linux重签名(得到新的重签名安装包new.ipa)

isign -c ios_development.pem -k ios.key -p 描述文件.mobileprovision  -o new.ipa Runner.ipa

附:isign安装教程(https://github.com/apperian/isign)

./version.sh
python setup.py build
python setup.py install

isign.zip

4. IPA分发

创建后缀为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>itemskey>
        <array>
                <dict>
                        <key>assetskey>
                        <array>
                                <dict>
                                    <key>kindkey>
                                    <string>software-packagestring>
                                    <key>urlkey>
                                    <string>https://重签名后的ipa下载地址string>
                                dict>
                        array>
                        <key>metadatakey>
                        <dict>
                            <key>bundle-identifierkey>
                            <string>com.togettoyou.appstring>
                            <key>bundle-versionkey>
                            <string>1.0.0string>
                            <key>kindkey>
                            <string>softwarestring>
                            <key>titlekey>
                            <string>Appstring>
                        dict>
                dict>
        array>
dict>
plist>

安装用户需在Safari浏览器访问如下html:

<a href="itms-services://?action=download-manifest&url={{ .plist下载地址 }}">安装APPa>

image.png

附:常见问题

  1. 使用进行重签名时,如下报错:
Traceback (most recent call last):
  File "/bin/isign", line 4, in <module>
    __import__('pkg_resources').run_script('isign==1.6.15.1589436891.dev72+root', 'isign')
  File "/usr/lib/python2.7/site-packages/pkg_resources/__init__.py", line 666, in run_script
    self.require(requires)[0].run_script(script_name, ns)
  File "/usr/lib/python2.7/site-packages/pkg_resources/__init__.py", line 1462, in run_script
    exec(code, namespace, namespace)
  File "/usr/lib/python2.7/site-packages/isign-1.6.15.1589436891.dev72+root-py2.7.egg/EGG-INFO/scripts/isign", line 199, in <module>
    isign.resign(app_path, **kwargs)
  File "/usr/lib/python2.7/site-packages/isign-1.6.15.1589436891.dev72+root-py2.7.egg/isign/isign.py", line 79, in resign
    alternate_entitlements_path)
  File "/usr/lib/python2.7/site-packages/isign-1.6.15.1589436891.dev72+root-py2.7.egg/isign/archive.py", line 397, in resign
    ua = archive.unarchive_to_temp()
  File "/usr/lib/python2.7/site-packages/isign-1.6.15.1589436891.dev72+root-py2.7.egg/isign/archive.py", line 262, in unarchive_to_temp
    process_watchkit(app_dir, REMOVE_WATCHKIT)
  File "/usr/lib/python2.7/site-packages/isign-1.6.15.1589436891.dev72+root-py2.7.egg/isign/archive.py", line 78, in process_watchkit
    watchkit_paths = get_watchkit_paths(root_bundle_path)
  File "/usr/lib/python2.7/site-packages/isign-1.6.15.1589436891.dev72+root-py2.7.egg/isign/archive.py", line 63, in get_watchkit_paths
    bundle = Bundle(path)
  File "/usr/lib/python2.7/site-packages/isign-1.6.15.1589436891.dev72+root-py2.7.egg/isign/bundle.py", line 55, in __init__
    log.debug(u"Missing/invalid CFBundleSupportedPlatforms value in {}".format(self.info_path))
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe9 in position 26: ordinal not in range(128)

这是Python的默认编码为ascii导致的,只需要改为utf-8即可解决。 直接在/usr/lib/python2.7/site-packages(具体路径结合实际情况)下新建一个sitecustomize.py文件(若不存在site-packages目录,则直接在/usr/lib/python2.7下创建):

# sitecustomize.py
# this file can be anywhere in your Python path,
# but it usually goes in ${pythondir}/lib/site-packages/
import sys
sys.setdefaultencoding('utf-8')

image.png

验证是否成功: 命令行输入python -c "import sys; print sys.getdefaultencoding()" image.png

Issues
  • 大佬请教下:打包是 Ad-Hoc 方式吗? 是不是1个开发者账号只能分发100台设备?

    大佬请教下:打包是 Ad-Hoc 方式吗? 是不是1个开发者账号只能分发100台设备?

    打包是 Ad-Hoc 方式吗? 是不是1个开发者账号只能分发100台设备?

    谢谢!

    opened by ieliwb 2
  • isign error

    isign error

    i change utf in isign ,but then it have an error /usr/lib/python2.7/site-packages/pyOpenSSL-18.0.0-py2.7.egg/OpenSSL/crypto.py:12: CryptographyDeprecationWarning: Python 2 is no longer supported by the Python core team. Support for it is now deprecated in cryptography, and will be removed in the next release.Traceback (most recent call last): File "/usr/bin/isign", line 4, in import('pkg_resources').run_script('isign==1.6.15.1616053653.dev76+root', 'isign') File "/usr/lib/python2.7/site-packages/pkg_resources/init.py", line 748, in run_script self.require(requires)[0].run_script(script_name, ns) File "/usr/lib/python2.7/site-packages/pkg_resources/init.py", line 1517, in run_script exec(code, namespace, namespace) File "/usr/lib/python2.7/site-packages/isign-1.6.15.1616053653.dev76+root-py2.7.egg/EGG-INFO/scripts/isign", line 199, in isign.resign(app_path, **kwargs) File "/usr/lib/python2.7/site-packages/isign-1.6.15.1616053653.dev76+root-py2.7.egg/isign/isign.py", line 79, in resign alternate_entitlements_path) File "/usr/lib/python2.7/site-packages/isign-1.6.15.1616053653.dev76+root-py2.7.egg/isign/archive.py", line 402, in resign ua.bundle.resign(signer, provisioning_profile, alternate_entitlements_path) File "/usr/lib/python2.7/site-packages/isign-1.6.15.1616053653.dev76+root-py2.7.egg/isign/bundle.py", line 259, in resign super(App, self).resign(signer) File "/usr/lib/python2.7/site-packages/isign-1.6.15.1616053653.dev76+root-py2.7.egg/isign/bundle.py", line 176, in resign self.sign(signer) File "/usr/lib/python2.7/site-packages/isign-1.6.15.1616053653.dev76+root-py2.7.egg/isign/bundle.py", line 171, in sign executable = self.signable_class(self, self.get_executable_path(), signer) File "/usr/lib/python2.7/site-packages/isign-1.6.15.1616053653.dev76+root-py2.7.egg/isign/signable.py", line 43, in init self.m = macho.MachoFile.parse_stream(self.f) File "/usr/lib/python2.7/site-packages/construct-2.5.2-py2.7.egg/construct/core.py", line 202, in parse_stream return self._parse(stream, Container()) File "/usr/lib/python2.7/site-packages/construct-2.5.2-py2.7.egg/construct/core.py", line 675, in _parse subobj = sc._parse(stream, context) File "/usr/lib/python2.7/site-packages/construct-2.5.2-py2.7.egg/construct/core.py", line 856, in _parse obj = self.cases.get(key, self.default)._parse(stream, context) File "/usr/lib/python2.7/site-packages/construct-2.5.2-py2.7.egg/construct/core.py", line 675, in _parse subobj = sc._parse(stream, context) File "/usr/lib/python2.7/site-packages/construct-2.5.2-py2.7.egg/construct/core.py", line 450, in _parse raise ArrayError("expected %d, found %d" % (count, c), sys.exc_info()[1])construct.core.ArrayError: ('expected 2, found 0', ArrayError('expected 68, found 67', ArrayError('expected 6, found 3', SwitchError('no default case defined',))))

    then i find solution that add new line in macho_cs.py

    CSMAGIC_UNKNOWN=0xfade7172, ####### NEW LINE INSERTED ######

    but it do not work ,how can i fix it , thanks

    opened by ridchan 2
  • There is a error When I run

    There is a error When I run "python setup.py install"

    Installed /usr/lib/python2.7/site-packages/isign-0.0.0.1619536073.dev14+root-py2.7.egg Processing dependencies for isign==0.0.0.1619536073.dev14+root Searching for cryptography>=2.2.1 Reading https://pypi.org/simple/cryptography/ Downloading https://files.pythonhosted.org/packages/9b/77/461087a514d2e8ece1c975d8216bc03f7048e6090c5166bc34115afdaa53/cryptography-3.4.7.tar.gz#sha256=3d10de8116d25649631977cb37da6cbdd2d6fa0e0281d014a5b7d337255ca713 Best match: cryptography 3.4.7 Processing cryptography-3.4.7.tar.gz Writing /tmp/easy_install-8EIEWQ/cryptography-3.4.7/setup.cfg Running cryptography-3.4.7/setup.py -q bdist_egg --dist-dir /tmp/easy_install-8EIEWQ/cryptography-3.4.7/egg-dist-tmp-M_gHVN Traceback (most recent call last): File "setup.py", line 55, in 'bin/isign_guess_mobileprovision.sh'] File "/usr/lib/python2.7/site-packages/setuptools/init.py", line 162, in setup return distutils.core.setup(**attrs) File "/usr/lib64/python2.7/distutils/core.py", line 152, in setup dist.run_commands() File "/usr/lib64/python2.7/distutils/dist.py", line 953, in run_commands self.run_command(cmd) File "/usr/lib64/python2.7/distutils/dist.py", line 972, in run_command cmd_obj.run() File "/usr/lib/python2.7/site-packages/setuptools/command/install.py", line 67, in run self.do_egg_install() File "/usr/lib/python2.7/site-packages/setuptools/command/install.py", line 117, in do_egg_install cmd.run(show_deprecation=False) File "/usr/lib/python2.7/site-packages/setuptools/command/easy_install.py", line 424, in run self.easy_install(spec, not self.no_deps) File "/usr/lib/python2.7/site-packages/setuptools/command/easy_install.py", line 666, in easy_install return self.install_item(None, spec, tmpdir, deps, True) File "/usr/lib/python2.7/site-packages/setuptools/command/easy_install.py", line 713, in install_item self.process_distribution(spec, dist, deps) File "/usr/lib/python2.7/site-packages/setuptools/command/easy_install.py", line 758, in process_distribution [requirement], self.local_index, self.easy_install File "/usr/lib/python2.7/site-packages/pkg_resources/init.py", line 782, in resolve replace_conflicting=replace_conflicting File "/usr/lib/python2.7/site-packages/pkg_resources/init.py", line 1065, in best_match return self.obtain(req, installer) File "/usr/lib/python2.7/site-packages/pkg_resources/init.py", line 1077, in obtain return installer(requirement) File "/usr/lib/python2.7/site-packages/setuptools/command/easy_install.py", line 685, in easy_install return self.install_item(spec, dist.location, tmpdir, deps) File "/usr/lib/python2.7/site-packages/setuptools/command/easy_install.py", line 711, in install_item dists = self.install_eggs(spec, download, tmpdir) File "/usr/lib/python2.7/site-packages/setuptools/command/easy_install.py", line 896, in install_eggs return self.build_and_install(setup_script, setup_base) File "/usr/lib/python2.7/site-packages/setuptools/command/easy_install.py", line 1164, in build_and_install self.run_setup(setup_script, setup_base, args) File "/usr/lib/python2.7/site-packages/setuptools/command/easy_install.py", line 1150, in run_setup run_setup(setup_script, args) File "/usr/lib/python2.7/site-packages/setuptools/sandbox.py", line 253, in run_setup raise File "/usr/lib64/python2.7/contextlib.py", line 35, in exit self.gen.throw(type, value, traceback) File "/usr/lib/python2.7/site-packages/setuptools/sandbox.py", line 195, in setup_context yield File "/usr/lib64/python2.7/contextlib.py", line 35, in exit self.gen.throw(type, value, traceback) File "/usr/lib/python2.7/site-packages/setuptools/sandbox.py", line 166, in save_modules saved_exc.resume() File "/usr/lib/python2.7/site-packages/setuptools/sandbox.py", line 141, in resume six.reraise(type, exc, self._tb) File "/usr/lib/python2.7/site-packages/setuptools/sandbox.py", line 154, in save_modules yield saved File "/usr/lib/python2.7/site-packages/setuptools/sandbox.py", line 195, in setup_context yield File "/usr/lib/python2.7/site-packages/setuptools/sandbox.py", line 250, in run_setup _execfile(setup_script, ns) File "/usr/lib/python2.7/site-packages/setuptools/sandbox.py", line 45, in _execfile exec(code, globals, locals) File "/tmp/easy_install-8EIEWQ/cryptography-3.4.7/setup.py", line 14, in package_name = path.basename(here) File "/usr/lib/python2.7/site-packages/setuptools_rust/init.py", line 8, in from .tomlgen import tomlgen_rust, find_rust_extensions File "/usr/lib/python2.7/site-packages/setuptools_rust/tomlgen.py", line 191 yield dep, toml.loads(f"{dep} = {options}")[dep] ^ SyntaxError: invalid syntax

    opened by fishmisswater 2
  • Get UDID时,request body中需要携带什么?

    Get UDID时,request body中需要携带什么?

    因为看到代码中有这段

    buf := make([]byte, 1024)
    n, err := c.Request.Body.Read(buf)
    defer c.Request.Body.Close()
    

    我尝试携带package_id,无果

    opened by reheroad 1
  • pull

    pull

    pull

    opened by kennylixi 1
  • v3 重构

    v3 重构

    opened by togettoyou 0
  • 解决描述文件未签名的方案

    解决描述文件未签名的方案

    go get github.com/19byte/amcs
    

    在上传iap包的时候,对mobileconfig进行签名即可

    err := amcs.Sign(mobileconfigPath,outPath,sslKeyPath,sslCrtPath,iosCrtPath)
    if err != nil {
        panic(err)
    }
    
    opened by 19byte 1
  • 如何实现下载完后自动跳转描述到文件?

    如何实现下载完后自动跳转描述到文件?

    市面上的好像都有这个功能,就是下载描述文件之后,自动跳转到安装页面

    opened by reheroad 0
  • isign签名报错

    isign签名报错

    isign 支持的xcode版本有要求么?用来签xcode 12.5打包的ipa文件,报错了

    opened by sunhao-baikal 6
  • 安装描述文件时显示

    安装描述文件时显示"未签名"

    image

    网上找到一个解决方案: https://www.jianshu.com/p/1aec2fdcbc14?utm_campaign=maleskine&utm_content=note&utm_medium=seo_notes&utm_source=recommendation 可以集成为描述文件签名的功能吗?

    opened by fishmisswater 4
Owner
寻寻觅觅
寻寻觅觅