本篇为这个系列的最后一篇,主要会讲重新签名和自动部署的问题。通过前面的步骤已经能够打包并签名了,现在回到系列开始时提到的问题:如何能够快速复制非常相似但又不同的包呢?其实,重新签名就能够做到!
重新签名和授权机制
我们知道苹果在打包的最后步骤就是进行签名,如果对于一个已经签名的包,我们还可以进行重新签名,签名的命令主要是codesign
。例如(摘自代码签名探析):
$ codesign -s 'iPhone Developer: Thomas Kollbach (7TPNXN7G6K)' Example.app 设置签名
如果你想为某一个 app 程序包重新设置签名,那么这个工具就很有用了。为了重新设置签名,你必须带上 -f 参数,有了这个参数,codesign 会用你选择的签名替换掉已经存在的那一个:
$ codesign -f -s 'iPhone Developer: Thomas Kollbach (7TPNXN7G6K)' Example.app 重新签名
$ codesign -vv -d Example.app 会列出一些有关 Example.app的签名信息 $ codesign --verify Example.app 会验证签名是否完好,若无任何输出则说明签名完好
除了重新签名的命令,还需要知道的是entitlements
–授权机制。授权机制决定了哪些系统资源在什么情况下允许被一个应用使用,即沙盒的配置列表。授权机制也是按照 plist 文件格式来列出的,Xcode 会将这个文件作为 –entitlements 参数的内容传给 codesign ,这个文件内部格式如下:
application-identifier
7TPNXN7G6K.ch.kollba.example
aps-environment
development
com.apple.developer.team-identifier
7TPNXN7G6K
com.apple.developer.ubiquity-container-identifiers
7TPNXN7G6K.ch.kollba.example
com.apple.developer.ubiquity-kvstore-identifier
7TPNXN7G6K.ch.kollba.example
com.apple.security.application-groups
group.ch.kollba.example
get-task-allow
在 Xcode 的 Capabilities 选项卡下选择一些选项之后,Xcode 就会生成这样一段 XML。 Xcode 会自动生成一个 .entitlements 文件,然后在需要的时候往里面添加条目。当构建整个应用时,这个文件也会提交给 codesign 作为应用所需要拥有哪些授权的参考。这些授权信息必须都在开发者中心的 App ID 中启用,并且包含在配置文件中,稍后我们会详细讨论这一点。在构建应用时需要使用的授权文件可以在 Xcode build setting 中的 code signing entitlements 中设置。
上面两段主要摘自《代码签名探析》,这是为了方便解释后面的重新签名脚本。
重新签名的实例
为了达到对一个app进行快速换皮的目的,我的设计思路如下图(本篇所有的Demo在这里)):
先自动打包生成一个母包,然后跑脚本对母包进行重新签名得到一个个子包。继续前面的PackageExample
例子,PackageExample
的bundleid为com.vienta.packageExample
,其作为母包,我又重新申请了两个bundleid,分别为com.vienta.packageExampleSon
和com.vienta.packageExampleDaughter
,作为重新签名后的子包。本例子主要通过通过换icon来反映”换皮”。
实例的Resign-ipa
文件夹目录结构如下图:
templates
文件夹中存放的是和授权文件相关的配置文件,build
文件夹主要存放最后被重新签名的包,module
目录下存放重签名的配置文件、资源文件和描述文件等。这里的思路是遍历module
文件夹下的内容,然后根据内容重新签名母包,将得到的包放到build
,例子中是两个,实际如果版本非常多,只需要向module
增加文件夹及配置内容即可。
resign.sh
为重签名的脚本,内容也不算多:
#!/bin/bash
for file2 in `ls -a ./module`
do
if [ x"$file2" != x"." -a x"$file2" != x".." -a x"$file2" != x".DS_Store" ]; then
echo $file2
#Conf file
CONF=./module/$file2/resign.conf
echo $CONF
#Datetime
NOW=$(date +"%Y%m%d_%s")
#Load config
if [ -f ${CONF} ]; then
. ${CONF}
fi
#Temp
TEMP="temp"
if [ -e ${TEMP} ]; then
echo "ERROR: temp already exists"
exit 1
fi
#Check app ID
if [ -z ${APP_ID} ]; then
echo "ERROR: missing APP_ID"
exit 1
fi
echo ${APP_ID}
#Create build dir
if [[ ! -d ${BUILD_PATH} ]]; then
mkdir ${BUILD_PATH}
fi
#Copy mother package
if [[ ! -f "../Package/ipa/QA/packageExample.ipa" ]]; then
echo "mother package not exists"
exit 1
fi
cp ../Package/ipa/QA/packageExample.ipa ./module/$file2${ASSETS_PATH}/packageExample.ipa
#Unzip the mother ipa
echo "Unzip ipa"
unzip -q ./module/$file2${ASSETS_PATH}${IPA_NAME}.ipa -d ${TEMP}
#Remove old Codesignature
echo "Remove old CodeSignature"
rm -r "${TEMP}/Payload/${APP_NAME}.app/_CodeSignature" "${TEMP}/Payload/${APP_NAME}.app/CodeResources" 2> /dev/null | true
#Replace embedded mobil provisioning profile
echo "Replace embedded mobile provisioning profile"
cp "./module/$file2${ASSETS_PATH}${PROFILE_NAME}.mobileprovision" "${TEMP}/Payload/${APP_NAME}.app/embedded.mobileprovision"
#Change icon
echo "Change icon"
cp "./module/$file2${ASSETS_PATH}/icon_120.png" "${TEMP}/Payload/${APP_NAME}.app/[email protected]"
cp "./module/$file2${ASSETS_PATH}/icon_180.png" "${TEMP}/Payload/${APP_NAME}.app/[email protected]"
#Change Bundleversion
if [[ ! -z ${APP_BUNDLE_VERSION} ]]; then
/usr/libexec/PlistBuddy -c "Set CFBundleVersion ${APP_BUNDLE_VERSION}" ${TEMP}/Payload/${APP_NAME}.app/Info.plist
fi
#Change CFBundleShortVersionString
if [[ ! -z ${APP_BUNDLE_SHORT_VERSION_STRING} ]]; then
/usr/libexec/PlistBuddy -c "Set CFBundleShortVersionString ${APP_BUNDLE_SHORT_VERSION_STRING}" ${TEMP}/Payload/${APP_NAME}.app/Info.plist
fi
#Change Bundleidentifier
/usr/libexec/PlistBuddy -c "Set CFBundleIdentifier ${APP_ID}" ${TEMP}/Payload/${APP_NAME}.app/Info.plist
#Create entitlements from template
ENTITLEMENTS=$(<./templates/entitlements.template)
ENTITLEMENTS=${ENTITLEMENTS//#APP_ID#/$APP_ID}
ENTITLEMENTS=${ENTITLEMENTS//#APP_PREFIX#/$APP_PREFIX}
echo ${ENTITLEMENTS} > ${TEMP}/entitlements.temp
#Re-sign
#这里注意命令参数的不同
#/usr/bin/codesign -f -s "${CERTIFICATE_TYPE}: ${CERTIFICATE_NAME}" --identifier "${APP_ID}" --entitlements "${TEMP}/entitlements.temp" --resource-rules "${TEMP}/Payload/${APP_NAME}.app/ResourceRules.plist" "${TEMP}/Payload/${APP_NAME}.app"
/usr/bin/codesign -f -s "${CERTIFICATE_TYPE}: ${CERTIFICATE_NAME}" --identifier "${APP_ID}" --entitlements "${TEMP}/entitlements.temp" "${TEMP}/Payload/${APP_NAME}.app"
#Remove copyed mother package
echo "Remove mother package"
rm -rf ./module/$file2${ASSETS_PATH}packageExample.ipa
#Re-package
echo "Re-package"
cd ${TEMP}
zip -qr "${IPA_NAME}_resigned_${NOW}.ipa" Payload
mv ${IPA_NAME}_resigned_${NOW}.ipa ../${BUILD_PATH}/${IPA_NAME}_${file2}_${NOW}.ipa
#Remove temp
cd ../
rm -rf ${TEMP}
fi
done
exit 0
代码中已经做了注释,做简单解释:
- 最外面对
module
文件夹for循环 - 读取
conf
配置文件(这些配置文件均是自己配置,实际也可以是plist文件,也比较方便) - 把母包从外面拷贝进来
- 对拷贝过来的包进行解压,移除
CodeResources
等,替换描述文件,替换icon,修改BundleId及版本信息,修改授权文件(授权文件也是需要注意的点,inhouse和develop也有一点点区别,但整体没变) - 然后就是
codesign
命令,代码注释也指出了--resource-rules
参数的问题,我原本找到的是带这个参数的,但是现在用不到。具体原因《代码签名探析》这里说是:“伴随 OS X 10.10 DP 5 和 10.9.5 版本的发布,苹果改变了代码签名的格式,也改变了有关资源的规则。如果你使用10.9.5或者更高版本的 codesign 工具,在 CodeResources 文件中会有4个不同区域,其中的 rules 和 files 是为老版本准备的,而 files2 和 rules2是为新的第二版的代码签名准备的。最主要的区别是在新版本中你无法再将某些资源文件排除在代码签名之外,在过去你是可以的,只要在被设置签名的程序包中添加一个名为 ResourceRules.plist 的文件,这个文件会规定哪些资源文件在检查代码签名是否完好时应该被忽略。但是在新版本的代码签名中,这种做法不再有效。所有的代码文件和资源文件都必须设置签名,不再可以有例外。” 。简单的说,资源文件现在也必须重新签名了。 - 重新压缩包,并移动到
build
中。
整个流程走完后我们在build
中得到了重新签名后的包,并且可以通过iTunes按照到设备上且能够正常打开,但是我们发现icon被换掉了,内部的bundleId、版本号信息等也被换了,于是乎,就这样轻轻松松的“换皮了”。我的手机上最后的截图如下:
这样做的优势:
- 节省劳动
- 配置方便,例子中主要修改icon,其实还可以加一些配置文件来配置颜色啊字体啊文字啊等等
- 对于邪恶点的公司,申请很多app账号,然后用这个方法,快速换皮,app内部的内容接口根据bundleid等信息来配置,里面放放广告,对主版本进行导流量等。
自动部署
截止到目前为止,成功的导出了各种各样的包。离完全的自动化“只剩最后一公里“了,还可以继续的进行自动部署来完成这最后一公里。通过自动部署,我们可以直接将包发到AppStore、fir\蒲公英这样的第三方平台、以及自己的服务器上。这里重点推荐mattt大神的——SHENZHEN。
SHENZHEN的安装和使用
通过gem安装
$ gem install shenzhen
具体的用法可以参见这里,毕竟在中国,主要提下FIR和蒲公英已经上传AppStore的命令:
FIR
$ ipa distribute:fir -u USER_TOKEN -a APP_ID
蒲公英 (PGYER)
$ ipa distribute:pgyer -u USER_KEY -a APP_KEY
USER信息到各自注册账号查找
iTunes Connect Distribution
$ ipa distribute:itunesconnect -a [email protected] -p myitunesconnectpassword -i appleid --upload
我们仍然是可以通过读取配置信息,来写个脚本跑部署,这部分就不再举例了,如果你不小心看到这个系列我觉得你应该会了,或者我们也可以相互商讨(毕竟我的blog人读的少 o(╯□╰)o)。实际上,我的同事已经实现了。
论持续化集成
虽然iOS好像能使用Jenkins进行持续化集成(好像我也用了一下Jenkins,貌似不是很好用,可能是我没有坚持用吧),但是通过这个序列的文章,其实我们自己就实现了一套持续化集成了。刚开始用蒲公英那会儿我把ipa包传给他们,他们就能放到平台给其他人测试,我觉得好神奇啊,后来想想,无非就是重新签名。持续化集成其实我的同事也实现了,我们用了一台服务器,定时的拉取代码,跑脚本,然后上传到测试服务器供人下载使用。只是公司内部推广不好,毕竟不是大厂也好像不是那么工程师文化,所以巴拉巴拉。
结语
大半年前就想写这系列的文章了,当时研究了,但是由于懒和忙其他的就一直没有写,现在终于写完了。回顾一下,还是能了解不少东西的,加密,签名,打包,重签名,部署等等。这些可能是些冷知识吧,相比较什么runloop、runtime、gcd啊要冷的多的多。我觉得记录下来挺有意思的。
本系列参阅和参考的不完全统计有:
- SSL(https)中的对称加密和非对称加密
- RSA算法原理(一)
- Bypassing OpenSSL Certificate Pinning in iOS Apps
- Understanding provisioning profiles and certificates
- Code Signing explained
- Mach-O可执行文件
- iOS开发中的各种证书
- 代码签名探析
- 苹果证书和公钥私钥加密
- iOS8以后CodeSign失效问题
- iOS证书及ipa包重签名探究
- ipa包部署网页安装
- iOS Code Signing: Under The Hood
- How iOS developers use code signing to get their apps on iPhones
- iOS Code Signing 学习笔记
- 苹果开发者账号那些事儿(二)
- Inside Code Signing
- 公开密钥加密
- 数字签名