Mac系统下应用重签名技术

4,176 阅读7分钟

Github 开源项目地址

github.com/iamlion/3To…

概述

做为一个项目驱动的公司,在管理APP描述文件的时候,时常会遇到描述文件过期的问题,产生的影响是导致客户端应用无法使用,这时候用户会不知所措,给用户的体验造成影响,甚至会影响APP的流量。而大多数解决方案,是利用市场上的重签名工具对应用重新签名发布,或者利用Xcode重新打包发布,这对开发者来说极不友好。我们绝不能亡羊补牢,必须在风险到来前提前准备防御,在用户无感知的情况下修复问题。出于此目的,应用重签名技术便有了它的意义,如果你是还没有研究过或者正在研究重签名技术的话,那这篇文章会非常适合你。

前置条件

iOS应用重签名具有一定的前置条件和软件依赖,并不是跨平台的,至少这套方案不能在 Window 上使用。

依赖条件
  • MacOS操作系统
  • Apple开发者账号
  • /usr/libexec/PlistBuddy (MacOS自带)
  • security (MacOS自带)
  • codesign (MacOS自带)
  • zip
  • unzip

上述依赖条件为系统软件,如果 zipunzip 软件不存在的话可以通过 HomeBrew 进行安装.

brew install zip;
brew install unzip;

iPA 简述

做为一个移动开发者应该知道 Android 的应用安装包为*.apk,而iOS的应用安装包则为*.ipa,事实上这些安装包都是可以通过unzip命令进行解压缩的压缩包,提取出来的文件是应用所需要的资源文件、应用配置文件、应用描述文件和应用二进制文件等,其中应用描述文件则是我们这次操作的目标。

过期应用描述文件

除了应用描述文件需要关注外,在iPA中还存在一类应用,那就是 appex。这类应用为宿主应用的拓展应用,appex同样存在自己的描述文件,如果宿主应用描述文件更新成功,但是appex应用没有更新描述文件的话,同样是签名失败的。

微信拓展应用

总结,如果应用需要重签名的话,则需要判断是否存在appex应用,如果存在则需要对每一个 appex 目录进行重签名,最后对整体目录进行重签名,如果不存在的话则只需要对主目录进行重签名即可。

我们可以在爱思助手下载应用的iPA包,然后通过可视化软件进行解压。

解压缩

描述文件中 BundleId 的能力

每一个 BundleId 都会有对应的 Capabilities,签名目标所拥有的能力一定不能比当前新描述文件所具备的能力少,如果新描述文件所具备的能力少于目标签名的对象,则签名同样失败。

BundleId

重签名核心操作

重签名的核心操作是 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.

操作简述

分析完毕后,设计思路应该是:

  1. 解压目标 ipa包到临时目录
  2. 将应用下的embedded.mobileprovision替换为新的描述文件
  3. 将新描述文件转换为info.plist文件到临时目录
  4. 从上面操作后的info.plist中提取TeamName, Entitlements信息
  5. 最后利用重签名的核心操作对目录进行重签名
  6. 上述过程中的第2,3,4,5步,如果appex应用存在也需要重新同样的操作,如果不存在则不需要执行
  7. 签名完毕后,利用zip命令对应用重新打包成ipa文件
  8. 删除临时目录生成的垃圾文件

到此为止重签名的操作就完成了,我们利用 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 开源项目地址

github.com/iamlion/3To…