0x0 前言
我所在的项目里, ios的工程一直都是使用纯OC来编写的, 以前一直都相安无事. 最近来了一位新同学, 一边感叹我们为什么不切换使用swift, 一边给工程里引了一些开源的组件, 开始我其实也没怎么在意, 不就混编嘛, 在本身开发调试运行一直都很顺利, 仿佛一切都没有发生过一样.
直到把工程放到Jenkins中编译, 问题才开始多起来.
内容公司对于证书签名等有比较严格的控制, 我们对外发布的应用都需要经过打包平台(且称为S平台)构建, 然后我们再上传到app store. 这样子安全是安全了, 可不太方便于打包过程里做一些订制化的操作, 不过还好, 平台开放了一个自定义的脚本, 可以让我们在上面订制一些自己的打包操作, 但唯一不好的是, 我们并不能拿到签名证书私钥, 所以要测试打包, 怎么样都需要上传代码到gitlab上, 再进行打包测试.
0x1 分析
0x10 首遇问题
首先遇到的问题是混编之后首先就在S平台上构建失败了, 不过这问题还好, 主要是因为使用了swift5, 然鹅S平台上的xcode版本还停留在xcode10.1(成文时xcode11已发布), 就是因为平台更新得比较滞后导致编译失败, 而且错误的信息还极具迷惑性.
1 | Password:2019-09-18 10:58:58.663 xcodebuild[89208:11300732] +dataWithFirstBytes:1024 ofFile:"/Users/jenkins/Home/workspace/project/Pods/JDYThreadTrace/JDYThreadTrace.framework/.gitkeep" failed, errno = 2 |
然后就一直去找.gitkeep究竟怎么错, 事实上跟这个文件根本没关系.
知道问题后, 解决办法也很简单, 尝试降低swift的版本要求, 并提醒S平台的更新编译器就行了.
0x11 再遇问题
经历了项目版本要求降级以及打包平台的升级, 按理来说应该在S平台上打包应该就没啥大问题了, 毕竟环境上来说本地与构建系统上基本一样了, 事实上在准备提审的时候也是能正常出包的, 有问题的是在提审的时候.
0x111 提审
这里有个小插曲, 由于xcode11已经把Application Loader移走了, 需要使用命令上传, 所以是不能直接像以前一样上传包的, 虽然可以从旧版本的xcode中撮出来(也能用), 但按经验来说这类被移走的功能肯定有它自己的问题, 想着还是按规范要求来好, 所幸的是使用命令上传本身也不复杂, 就一行指令就行了.
1 | xcrun altool --upload-app -type ios -u {account} -p {password} --file {ipa路径} |
然后就跟Application Loader一样等待上传即可, 正常来说很快就能出来结果.
事实上也还真的很快就有结果, 不过是失败结果.
The binary file ‘xxxx.app/libswiftRemoteMirror.dylib’ is not permitted. Your app can’t contain standalone executables or libraries, other than a valid CFBundleExecutable of supported bundles.
问题很明显, 包里有一个libswiftRemoteMirror.dylib的东西苹果认为不应该存在, 直接就给打回了.
0x112 第一次解决
把包文件下下来一看, 果然苹果诚不欺我, 果然有这么一个文件在这里
既然都提示文件多余了, 那我直接把它删除掉行不行呢? 因为构建的过程是在S平台上进行的, 我需要删除即就只能在自定义的打包脚本里删除. 由于打包脚本里是通过xcodebuild先构建出app文件,再自行压制成ipa的, 我寻思着就需要在生成app后直接删除掉包里的对应文件即可. 因为app其实也可以当成一个文件夹看, 直接使用rm命令即可
1 | rm xxx.app/libswiftRemoteMirror.dylib |
完事重新打包, 嗯…..文件是没了, 可包也装不上了.
0x113 环境升级
包都装不上了肯定是算不上解决的, 我想包装不上主要还是因为签名的原因, 因为我删除了文件实际上在安装的过程中文件的签名校验就对不上了, 装不上是自然的事.
既然从包自身的角度出发解决不了问题, 那能否从构建环境上使点法子呢. 是有的, 上文说过在上一次出问题时提醒S平台的维护同学去升级xcode, 不知道升级没有, 于是先尝试在打包脚本里加了一行测试代码, 目的是为了检测是否有安装.
因为原来的打包平台里本身也有不同xcode版本的切换, 只需要设置对应的变量即可
1 | xcodeSelectPath="$HOME/bin/selectXcode10" |
于是我只需要探测$HOME/bin下是否存在xcode11就可以了
1 | ls $HOME/bin/|grep Xcode |
结果很满意, 维护的同学已经把平台升级了, 顺利地切换到了使用xcode11编译, 且编译出来的包里已经没了libswiftRemoteMirror.dylib, 满心欢喜地上传且上传成功了, 没想到如此简单地就解决!!
0x114 打回
是的, 打回了, 打回的信息里这么写
ITMS-90426: Invalid Swift Support - The SwiftSupport folder is missing. Rebuild your app using the current public (GM) version of Xcode and resubmit it.
还是跟swift的支持库有关, 可为嘛libswiftRemoteMirror.dylib说不要了, 现在又来拒绝呢?再说这个所谓的”SwiftSupport folder”长啥样我根本没见过, 不知道正确的形态是怎么样的.
一通谷歌, 有说把Build Settings里的”Always Embed Swift Standard Libraries”设置为NO, 还有的说还要把PROJECT里的同样设置设置为YES
可是对于我这种情况这些都不work.
还有另外一种说法是使用xcodebuild的archive和exportArchive来直接导出ipa, 这种做法我感觉没毛病, 但遇到两个问题
- 构建脚本里的过程并非通过archive来导出ipa的, 脚本需要大改
- 我没有, 也不知道签名
基于这两点, 这个方案一开始我没采用, 而是选择在原来删除libswiftRemoteMirror.dylib的方案上再进一步, 既然上面已经识别到可能是因为签名的原因, 那能不能在没获取到证书私钥的情况下重签名呢.
0x115 重签名
那肯定是不行的, 你证书都没签条毛啊. 事实上也的确如此, 我自己签那是肯定不行的, 那如果让打包机器自己签呢?由于打包机器里肯定不会给jenkins用户太大的权限, 像要装什么东西啊查什么东西估计都会受限, 于是在下手重签时我需要准备点资料.
众所周知, 要在机器上签名, 那本地的钥匙串里需要有对应证书的私钥, 我如果能找出(或者有标识让机器找出)对应私钥在钥匙串中的位置, 那我重签就有希望了.
0x1151 获取证书名
因为我账号已经加入到公司的开发团队里去了, 可以很方便地看到公司开发者账号的信息, 如账号的名称, TeamId等等, 而构建证书名字的信息, 有这些就已经足够了.
看一下自己机器里的钥匙串, 证书名一般长这样.
猜测组成的结构如下
iPhone Distribution: {公司名} ({TeamId})
其中iPhone Distribution指代的就是发布证书, 剩余的两个信息都已经可以获取到了, 那么就可以构建出我想要的签名证书的名字了.
以上是针对发布包的, 因为我只加入到了生产的团队里, 可打包平台还能打出企业包来, 当然如果没有企业包也行, 毕竟我们只是面向上线, 但如果没有企业包辅助验证, 就不知道这个方案到底可行不可行. 没办法, 还是得把企业包的证书名给获取到.
由于管理企业包的账号与生产包的开发者账号是独立的, 所以我无法得知查看企业包的账号是长什么样, 所幸的是, 以前在配置企业包时, 我知道了TeamId的信息, 这么说我只需要知道对应的公司名(账号名)就可以构建出证书来了. 但根据已有信息, 我根本无法知道账号名叫啥.
还好我灵机一动, 以往在测试企业包时, 经常会弹出这样子的信息
这不就是对应证书的名字吗!?, 喜出望外, 这样子生产包与企业包的对应证书名我都可以构建出来了.
0x1152 获取授权信息
签名需要传入授权信息, 这个授权信息可以理解为mobileprovision里的一个段, 说是授权信息, 其实就是一些基本是包信息而已, 要从mobilieprovision里读取entitlements信息,可以使用security命令
1 | security cms -D -i ../app.mobileprovision > entitlements_full.plist |
可以获取到一个plist文件, 里面描述了provision的很多信息, 而我们重签名, 只需要使用Entitlements的字段就行, 可以使用PlistBuddy单独把Entitlements字段提取出来.
1 | /usr/libexec/PlistBuddy -x -c 'Print:Entitlements' entitlements_full.plist > entitlements.plist |
得到类似以下的描述
1 | <key>Entitlements</key> |
但如果我们没有mobileprovision文件怎么办?也好办, 在app文件里会包含mobileprovision文件, 名字固定为embedded.mobileprovision, 可以直接从这里面去获取.
0x0153 执行重签
完成的重签命令如下:
1 | cp "${DEFS_TARGET}.app/embedded.mobileprovision" ${DEFS_TARGET}.mobileprovision |
正常来说这样子的步骤就可以重签了……..
然鹅依然还是不行, 提示
iPhone: ambiguous (matches “iPhone Distribution: xxxxxx (xxxxxx)” and “iPhone Developer: Jyyyyyyyy (yyyyyyyyyyyyy)” in /Users/jenkins/Library/Keychains/login.keychain-db)
……简直是要疯了…….
看错误信息是只是单纯地拿证书名字去钥匙串里获取, 有可能会获取到多个切尔西的证书出来…..这特么怎么办?难道只能爆破掉打包机器上的钥匙串吗……
没办法只能研究一下怎么去获取钥匙串里的信息了, 查到发现其实就是上面用过的security命令, 通过查看help, 我总结出其实钥匙串里的item都有一个hashcode对应, 那么利用这个hash值, 能不能替代证书的名字用于签名呢?
答案是可以的, 通过security的find-identity命令, 可以获取到指定的一类证书及私钥的信息. 其中, 我们可以加一个过滤条件来筛选信息, find-identity可以过滤包括basic, ssl-client, ssl-server, smime, eap, ipsec, ichat, codesigning, sys-default, sys-kerberos-kdc, macappstore, appleID 等类型的item, 因为我们是需要签名, 需要只需要过滤codesigning就可以了.
执行如下命令
1 | security find-identity -v -p codesigning |
可以找到类似的结果
从这个列表里我们可以很容易看出用用于真实签名的证书究竟是哪个(看名字), 其中前面的串就是其对应的id值了.
最终使用id来替换证书名, 再执行重签就可以了.
1 | ...... |
此时在打包平台上的重签名过程可以告一段落了. 再次上传包等待扫描结果. 不一向, 马上就失败了, 连扫描的一关都过不了, 提示如下:
ERROR ITMS-90046: “Invalid Code Signing Entitlements. Your application bundle’s signature contains code signing entitlements that are not supported on iOS. Specifically, value ‘*’ for key ‘com.apple.developer.associated-domains’ in ‘Payload/XXX.app/xxx’ is not supported.”
因为我在配置provision时配置了associated-domains, 但在构建 entitlements.plist时, 里面的associated-domains是
1 | <key>com.apple.developer.associated-domains</key> |
这个问题还是比较好解的, 只需要使用PlistBuddy对于这个key的值重新赋值就行了.
1 | /usr/libexec/PlistBuddy -c 'Delete :com.apple.developer.associated-domains' entitlements.plist |
0x12 再次打回
此文不结束表示这问题就还没解决……是的又打回了, 又说包里没有swiftSupport的……
问题又回到了原点. 看来只是单纯地针对原来的包重新签名也不能解决问题, 其根本是因为包里缺少所谓的SwiftSupport文件.
那么得换种思路了, 针对原来的脚本打出来的包去做调整不行, 那能不能直接换种打包的方法.
0x121 脚本分析
原来使用的脚本虽然是使用xcodebuild,但并非直接输出ipa, 而是输出app, 最终通过zip直接压成ipa.
1 | xcodebuild -workspace ../$workspacefile -scheme $buildTarget -destination $destination -configuration $build_configuration -derivedDataPath ../DerivedData -UseModernBuildSystem=NO |
其是基于app中的一个简单封装成ipa而已, 如果苹果说的所谓 SwiftSupport 是在ipa的其他地方的目录文件的话, 那这种打包方法天生就是有缺陷的, 那就只能换一种打包方法了.
0x122 Archive
Archive是Xcode里的一种打包方式, 使用archive可以直接打出一个ipa, 省去了中间的环节, 如果编译器认为是需要有SwiftSupport的话, 那使用这种方式打包就肯定会把所有想关的东西打进去.对应到命令行中, 也是使用xcodebuild来执行的.
执行的命令与原来的有点类似, 只不过现在是使用archive与exportArchive模式.
1 | xcodebuild -workspace ../$workspacefile -scheme $buildTarget -destination $destination -configuration $build_configuration -archivePath ../ArchiveData/${DEFS_TARGET}.xcarchive -UseModernBuildSystem=NO archive |
其中, export.plist其实就类似重签名过程中entitlements.plist, 我们需要的信息大概如下:
1 |
|
其中, method是必须的, 因为我们需要在appstore中上架, 所以值为”app-store”, 其余的是如果是使用自动签名, 则不再需要写, 直接用项目中指定的即可. 如果要手动签名, 则需要带上.
这个文件里的想着信息可以由重签名的那一节里获取, 这里使用PlistBuddy生成一个plist文件.
1 | echo "<plist version="1.0"><dict></dict></plist>">../export.plist |
生成文件后, 再使用上面的命令archive一个包, 最终的产物就是ipa.
打出来的文件结构是这样子的, 这个包比一般的包要大上两倍多, 可以看到里面的文件结构, 包大小的增量是就是体现在我们一直想要的swiftSupport上.
这次这个包就能顺利过通过苹果的扫描检查了.
0x3 总结
最终解决这个问题就是通过使用archive的命令来打ipa解决的, 但这过程中更重要的是怎么在没办法获取到打包机器的签名信息的情况下去做跟签名有关的事情.