iOS脚本系列之App重签名

我们知道一个iOS项目发布的完整流程是

Build->签名->打包ipa->上传AppStore

今天我们就来研究签名这个过程,并用脚本实现重签名。

重签名一般用来分发应用,比如我们前面上传蒲公英的脚本,在打包ipa之前,用企业证书进行重新签名,这样上传到蒲公英的ipa包所有人都能安装,没有设备闲置

另外一种用途就是破解别人的App,然后重新打包签名。

准备文件

App文件

一般来说,只要App文件就可以,不过一般我们可以从下面的途径获得

  • 用xcode编译产生的App,或者用命令xcodebuild的到的app文件
  • 从AppStore下载,解压安装包得到的App文件
  • 越狱渠道下载ipa,解包得到App文件

Bundlle Id

这个不用多说,没有bundleId 一切免谈

mobileprovision 配置文件

同样不必多说,必须与bundleId对应,如果是development,必须包含测试设备才能安装

证书

开发证书提前安装到钥匙串,我们后面使用的时候直接使用其名称

entitlements.plist

授权文件,签名时必须要指定的配置文件,如果不指定这个文件,那么签名后的文件无法安装,格式如下,不包括所有的属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?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>application-identifier</key>
<string>CWD8U968T3.com.xxxx.xxxx</string>
<key>aps-environment</key>
<string>development</string>
<key>com.apple.developer.team-identifier</key>
<string>CWD8U968T3</string>
<key>get-task-allow</key>
<true/>
<key>keychain-access-groups</key>
<array>
<string>CWD8U968T3.com.xxxx.xxxx</string>
</array>
</dict>
</plist>

不同环境的签名,entitlements.plist里面的属性也是略有不同,比如distribute签名get-task-allow属性为false,并且新增了属性beta-reports-active 为trueaps-environment属性为production

那么这样一个文件,难道需要我们每次签名的时候,手动创建,并且修改里面这一堆属性吗,答案显然是否定的。其实整个entitlements信息已经包含在我们打包的mobileprovision 配置文件里面了。我们通过如下命令打印mobileprovision的信息到终端

1
security cms -D -i xxxxx.mobileprovision

输出内容为plist文件格式,其中包含entitlements字段,对应的内容就是我们上面entitlements.plist需要的内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
.........
</array>
<key>Entitlements</key> //这里
<dict>
<key>keychain-access-groups</key>
<array>
<string>K9BNJJYDPH.*</string>
</array>
<key>get-task-allow</key>
<true/>
<key>application-identifier</key>
<string>K9BNJJYDPH.com.fengkuang.yiyuanyungou</string>
<key>com.apple.developer.team-identifier</key>
<string>K9BNJJYDPH</string>
<key>aps-environment</key>
<string>development</string>
</dict>
<key>ExpirationDate</key>
<date>2017-07-17T10:13:39Z</date>
<key>Name</key>
...........

所以我们只需要导出Entitlements字段的内容为一个plist文件即可,这就用到了/usr/libexec/PlistBuddy命令,如下组合命令在当前目录生成一个entitlements.plist文件

1
/usr/libexec/PlistBuddy -x -c "print :Entitlements " /dev/stdin <<< $(security cms -D -i $APP_PATH/embedded.mobileprovision) > entitlements.plist

至此,签名所需要的内容都准备齐全,接下来进入签名流程

签名流程

替换embedded.mobileprovision

App会将mobileprovision配置文件打包进bundle,并以embedded.mobileprovision命名,我们要做的第一步就是用我们自己的mobileprovision,替换App内部的embedded.mobileprovision

1
cp $PROVISION_PATH $APP_PATH/embedded.mobileprovision

生成entitlements.plist

用我们准备的mobileprovision配置文件来生成授权文件entitlements.plist,由于我们第一步已经将我们准备的mobileprovision文件拷贝进App文件夹内,并重命名为embedded.mobileprovision,所以这里直接对embedded.mobileprovision解析生成entitlements.plist

1
/usr/libexec/PlistBuddy -x -c "print :Entitlements " /dev/stdin <<< $(security cms -D -i $APP_PATH/embedded.mobileprovision) > entitlements.plist

修改BundleId

App内部会包含一个info.plist文件,其实就是我们开发时的工程info配置文件。

1
/usr/libexec/PlistBuddy -c 'Set :CFBundleIdentifier $NEW_BUNDLE_ID' $APP_PATH/info.plist

重新签名

签名命令

1
codesign -f -s "$CER_NAME"  --entitlements entitlements.plist $APP_PATH

如上即完成了整个签名流程,打开pp助手,将安装包丢进去,即可成功安装到手机。

过程分析

_CodeSignature

App签名过程中最重要的两个文件

  • _CodeSignature 文件夹
  • embedded.mobileprovision

_CodeSignature文件夹在App顶层目录,打开_CodeSignature看到CodeResources文件,用文本编辑器打开,发现其是一个plist文件,保存了App目录里面所有文件的签名(包括embedded.mobileprovision,info.plist)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?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>files</key>
<dict>
<key>AboutViewController~iphone.nib/objects-8.0+.nib</key>
<data>
GyPhI8KatWGryCh+GVl7V0DeFHc=
</data>
<key>AboutViewController~iphone.nib/runtime.nib</key>
<data>
uPnWXqdN4oWBfzvlzX1LTIeGHY4=
</data>
<key>AddressDetailViewController~iphone.nib/objects-8.0+.nib</key>
<data>
rVOD8E0uEHb9iMbcGGRj1E9TUWE=
</data>
.........

当我们替换embedded.mobileprovision,修改info.plist之后,再调用codesign命令的时候 重新生成了_CodeSignature。

iOS系统启动App的时候会检查_CodeSignature,以确保包的资源是没有被篡改过的。

entitlements.plist

这个授权文件其实已经包含在embedded.mobileprovision文件里面了,按照逻辑来讲,不用提供也可以,但是实际测试,还是必须要提供,不然签名后的App无法安装。

观察xcode的build或者archive的log信息

发现无论是build还是archive,xcode在签名的时候都指定了entitlements文件,我们可以复制这里的xxx.xcent路径然后,从finder中寻找打开,可以发现其内容与我们上面介绍的entitlements.plist内容是一致的。

所以我这里的签名流程果断跟xcode一致,使用entitlements.plist