Github 开源项目地址
概述
做为一个项目驱动的公司,在管理APP描述文件的时候,时常会遇到描述文件过期的问题,产生的影响是导致客户端应用无法使用,这时候用户会不知所措,给用户的体验造成影响,甚至会影响APP的流量。而大多数解决方案,是利用市场上的重签名工具对应用重新签名发布,或者利用Xcode重新打包发布,这对开发者来说极不友好。我们绝不能亡羊补牢,必须在风险到来前提前准备防御,在用户无感知的情况下修复问题。出于此目的,应用重签名技术便有了它的意义,如果你是还没有研究过或者正在研究重签名技术的话,那这篇文章会非常适合你。
前置条件
iOS应用重签名具有一定的前置条件和软件依赖,并不是跨平台的,至少这套方案不能在 Window 上使用。
依赖条件
- MacOS操作系统
- Apple开发者账号
- /usr/libexec/PlistBuddy (MacOS自带)
- security (MacOS自带)
- codesign (MacOS自带)
- zip
- unzip
上述依赖条件为系统软件,如果 zip
和 unzip
软件不存在的话可以通过 HomeBrew
进行安装.
brew install zip;
brew install unzip;
iPA 简述
做为一个移动开发者应该知道 Android 的应用安装包为*.apk
,而iOS的应用安装包则为*.ipa
,事实上这些安装包都是可以通过unzip
命令进行解压缩的压缩包,提取出来的文件是应用所需要的资源文件、应用配置文件、应用描述文件和应用二进制文件等,其中应用描述文件则是我们这次操作的目标。
除了应用描述文件需要关注外,在iPA中还存在一类应用,那就是 appex
。这类应用为宿主应用的拓展应用,appex
同样存在自己的描述文件,如果宿主应用描述文件更新成功,但是appex
应用没有更新描述文件的话,同样是签名失败的。
总结,如果应用需要重签名的话,则需要判断是否存在appex
应用,如果存在则需要对每一个 appex
目录进行重签名,最后对整体目录进行重签名,如果不存在的话则只需要对主目录进行重签名即可。
我们可以在爱思助手
下载应用的iPA包,然后通过可视化软件进行解压。
描述文件中 BundleId 的能力
每一个 BundleId 都会有对应的 Capabilities
,签名目标所拥有的能力一定不能比当前新描述文件所具备的能力少,如果新描述文件所具备的能力少于目标签名的对象,则签名同样失败。
重签名核心操作
重签名的核心操作是 codesign
,该命令是 MacOS 下自带的命令,主要用于代码签名。
签名命令:
codesign -f -s "DEVELOPER_TEAM" "PLISTFILE_PATH" "SIGN_DIRECTORY"
其中 -f
代表 force 强制更新,-s
代表 sign。
-f, --force
When signing, causes codesign to replace any existing signature on the path(s) given. Without
this option, existing signatures will not be replaced, and the signing operation fails.
-s, --sign identity
Sign the code at the path(s) given using this identity. See SIGNING IDENTITIES below.
操作简述
分析完毕后,设计思路应该是:
- 解压目标
ipa
包到临时目录 - 将应用下的
embedded.mobileprovision
替换为新的描述文件 - 将新描述文件转换为
info.plist
文件到临时目录 - 从上面操作后的
info.plist
中提取TeamName
,Entitlements
信息 - 最后利用重签名的核心操作对目录进行重签名
- 上述过程中的第
2
,3
,4
,5
步,如果appex
应用存在也需要重新同样的操作,如果不存在则不需要执行 - 签名完毕后,利用
zip
命令对应用重新打包成ipa
文件 - 删除临时目录生成的垃圾文件
到此为止重签名的操作就完成了,我们利用 Shell 脚本将操作实现。
第一步:创建临时目录
创建临时目录,用来保存操作过程中产生的垃圾
# 创建临时目录
ROOT_PATH=$(pwd)
DIR_TMP_PATH="${ROOT_PATH}/temp"
# 删除旧目录并创建临时目录
rm -rf $DIR_TMP_PATH
mkdir $DIR_TMP_PATH
第二步:解压目标iPA
将需要重签名ipa包目录,解压到临时文件
# 从用户输入的参数中获取 iPA 地址
PARAM_IPA_PATH=$1
# 解压到临时目录
unzip -d $DIR_TMP_PATH $PARAM_IPA_PATH
第三步:封装重签名操作过程
无论是宿主应用还是拓展应用,其重签名的操作过程其实是一样的,我们把其封装成方法
# 从描述文件中提取完整plist文件
_getPlistFile(){
local _path=$1
local _name=$2
local originMobileprovisionPath="${_path}/embedded.mobileprovision"
local tempEntitle="${DIR_ENTITLEMENTS_TMP_PATH}/${_name}_temp.plist"
local entitle="${DIR_ENTITLEMENTS_TMP_PATH}/${_name}.plist"
security cms -D -i "$originMobileprovisionPath" > "${tempEntitle}"
# 提取 Entitlements 字段
/usr/libexec/PlistBuddy -x -c 'Print:Entitlements' $tempEntitle > $entitle
}
# 重签名拓展应用
reSign(){
local _path=$1
local _appexName=$2
local _tmpMbArray=(${_appexName/./ })
local _tmpMbName=${_tmpMbArray[0]}
# 把新的描述文件替换旧版描述文件
local _mbPath="${_path}/embedded.mobileprovision";
rm -rf _mbPath
for index in $(seq 0 ${#PARAM_APPEXMOBILEPROVISION[@]})
do
local appexMb=${PARAM_APPEXMOBILEPROVISION[index]}
local _tmpStrArray=(${appexMb//// })
local last=${#_tmpStrArray[@]}
((last-=1))
if [ $last -ge 0 ]
then
local _newAppexMb=${_tmpStrArray[last]}
if [[ $_newAppexMb =~ $_tmpMbName ]];
then
# 复制新的文件到目标目录
cp $appexMb $_mbPath
break
fi
fi
done
_getPlistFile $_path $_tmpMbName
local _plistPath="${DIR_ENTITLEMENTS_TMP_PATH}/${_tmpMbName}.plist";
echo $PARAM_DEVELOPTEAM
echo $_plistPath
echo $_path
codesign -f -s "${PARAM_DEVELOPTEAM}" --entitlements "${_plistPath}" "${_path}"
}
第四步:递归遍历所有目录
我们需要对所有文件夹进行判断,并且进行重签名操作
# 递归遍历目录
recursivePath(){
local _path=$1;
for item in $(ls "$_path")
do
local subPath="${_path}/${item}";
if [[ ${item} =~ '.appex' ]];
then
# 对应用拓展进行重签名
reSign "${subPath}" $item
else
if [ -d "$subPath" ];
then
recursivePath $subPath
fi
fi
done
}
# 宿主应用进行重签名操作
reSign "宿主应用描述文件路径" "宿主应用根目录"
第五步:打包新的iPA包
重签名操作完成后,我们新的目录重新进行压缩打包。
cd "新的文件目录中,必须到 /Payload 目录级"
zip -r "New.ipa" ./Payload
mv ./New.ipa "输出目录"
第六步:删除垃圾文件
所有操作完成后,就可以删除垃圾文件,这样我们的重签名操作就完成了。
# 移除垃圾文件
rm -rf $DIR_TMP_PATH
拓展延伸
-
为什么需要设计 Shell 脚本,因为方便和 Jenkins 等平台对接,我们利用脚本可以在服务端进行定时判断,可以在应用过期一个月前提前通知到开发者,这样开发者只需要提前一个月上传新的描述文件,系统将会自动完成更新。
-
利用
PlistBuddy
命令可以设计出更多个性化的功能,例如可以自定义版本号,APP名称等。
问题补充
在之后的调研中,我发现上面有信息错误,主要在签名上面,TeamName参数默认会选取当前系统钥匙串中过期时间较长的开发者证书,如果当前系统存在两张一样的开发者证书,这时候会有发生签名失败,所以我们需要提供使用者可以选取的操作。
在当前PC下输入 security find-identity -v -p codesigning
命令,可以查找当前可用证书
lichXXXX:~ XXXX$ security find-identity -v -p codesigning
1) 6E8C2BD93EC549822A2E435E7ABC9D56921E950E "iPhone Developer: XX X (XXXXXX)"
2) 4A7FCBA4774460D8A233493FB702E8F68E532C1F "iPhone Distribution: Jiangsu XXXX XXXX XXXX co., Ltd."
3) 055299883D1C6085F607E048AF95A327F41E92FA "iPhone Distribution: Jiangsu XXXX XXXX XXXX co., Ltd."
4) A2813971EAB75B20825BF69A0BA38AB132F13058 "Mac Developer: XX X (XXXXXX)"
5) C50A86CB267ACA13D874F7F8B689852D539BB2D2 "Mac Developer: 1748439277@qq.com (9LM3QQV38R)"
6) A90B1137BD902385FA461408304C5FCE1FFC006B "iPhone Developer: 1748439277@qq.com (9LM3QQV38R)"
7) BB273AD0F9C13E70718879656C68901C2F2C99C0 "Apple Development: XX X (XXXXXX)"
8) 5FFEAA884F4DC2DBF9B16C536FA1841F8056602B "Apple Development: 1748439277@qq.com (9LM3QQV38R)"
8 valid identities found
如果当前系统下存在两张一样的开发者证书,这时候应该指定 identity
对证书签名,而不是简单用 TeamName
去签名,否则会签名失败。
codesign -f -s "证书的Identify(例如 055299883D1C6085F607E048AF95A327F41E92FA)" --entitlements "entitlements.plist信息路径" "签名目录"
确认描述文件和证书是否匹配
1 打印“新的描述文件”的Plist信息,并且获取 DeveloperCertificates 字段下的字符串
在控制输入以下命令打印“描述文件”的信息:
security cms -D -i 新描述文件的路径
2 新建一个 “test.cer” 文件,复制以下内容到文件中
-----BEGIN CERTIFICATE-----
将 DeveloperCertificates 字段中的 <data></data> 之间的内容拷贝至此
-----END CERTIFICATE-----
3 右键 “test.cer” 文件,点击快速查看
查看序列号
4 打开”系统钥匙串”,查找重复的证书信息
5 依次点击点击“显示简介”,找到和描述文件一致的序列号的证书
6 查找到正确的证书后,向下滑动找到“SHA-1”指纹值
7 找到对应指纹SHA-1值的证书就是和描述文件相匹配的证书
Github 开源项目地址
今天的文章Mac系统下应用重签名技术分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/15889.html