写点什么

iOS Release 版本开启调试功能

用户头像
liu_liu
关注
发布于: 2020 年 05 月 03 日
iOS Release 版本开启调试功能

iOS 开发中,对外发布的 TestFlight 版或者正式版都是以 Relase 方式构建。而我们一般会在 Debug 模式下开启某些调试功能或打印日志,但是这些操作在 Release 无效。



当我们需要在 Release 版本中启用调试功能时,就不太方便了。而解决这个问题似乎没有很优雅的方式。



在读过 https://andrewking.ca/2020/04/how-to-enable-custom-debugging-in-release-builds/?utm_campaign=iOS%2BDev%2BWeekly&utm_medium=email&utm_source=iOS%2BDev%2BWeekly%2BIssue%2B454 这篇文章后,发现一个比较新奇的方法。



常规方式



文章前半部分提供了几种可能的方式,比如:



  1. 使用一个新的配置定义 TESTFLIGHT



在发版本的时候打两个包,一个是开启 TESTFLIGHT 配置,另一个关闭 TESTFLIGHT。但这并不能很好的区分哪个包对应的是什么配置。



  1. 硬编码 userId



代码中维护 userId 列表,只对指定的 userId 生效。但添加/删除需要修改代码,且有些功能并不需要登录。这种方式可以进一步延伸到使用 deviceId,通过服务端下发列表。



  1. 检查安装来源。



区分 TestFlight/App Store/本地 build 几种安装方式。而苹果并没提供官方 API 来获取来源,作者提供了一个 hack 的方式,但可靠性有待确定,因为这种方式极大的依赖苹果实现。



  1. 使用秘密的手势。



使用某种复杂的手势来作为开关。但是如果有用户知道了这个手势,也可以调起调试功能。



这种方式让我想起上家公司有关某个功能调试开关的实现,思路比较好玩。



在 app 的某个页面,比如「设置 - 关于我们」(最好是无额外点击事件的页面)。把当前页面按照数字 0-9 分为 10 个虚拟区域,类似拨号键盘排布。如下图所示:





而密码就是当前的手机时间,以 4 位数字表示,不足补 0。在页面上按照预设区域输入当前时间(当前实际上看不到任何分隔线的,估摸大致区域),即可打开调试开关。



  1. 使用秘密的 url scheme



通过特定 url scheme 来唤起 app,以开启调试功能。但它有几个弊端:



  • 为了防止被破解,url 不能太简单,而且开发者需要记住该 url,调试不便。

  • 由于需要在浏览器中输入 url,那么意味着需要离开 app 进行操作,增加调试成本。

  • 如果有其他人意外知道了 url,同样也可以调试。

重头戏



下面重头戏来了,其方式是通过检测一个特殊的描述文件 Debug Profile 是否在机器上安装,来确定是否开启调试功能。



这种方式有如下好处:



  • 如果想要调试就必须有描述文件,而普通用户无法获取。

  • 只需安装一次描述文件。

  • 一个描述文件 可以被多个 app 使用,不同优先级的功能可以使用不同的描述文件。

  • App Store 安装的 app 也可以使用,因为只需要有描述文件。

  • 不需要特殊的环境和用户。



但是也有其坏处:



  • 需要创建存储证书、 描述文件。

  • 需要根据检查证书信任结果来确定是否安装了描述文件,并且作者承认,检查方式有一点 hack

  • 需要将描述文件存起来,不能丢失。



原理



这种方式是基于 SecTrustEvaluate 方法来验证证书是否受信任。



使用 SecTrustEvaluate 来确定用户是否安装且信任了证书(通过安装描述文件),还是仅仅打包在 app 中未受信任的证书。



因此我们需要做的事情如下:



  • 创建证书

  • 创建和安装描述文件

  • 检查证书是否被信任



下面我们来一步步的操作。

1. 创建证书



  • 打开证书助理,选择创建证书颁发机构。





  • 填入名称和邮箱,反选 Make this CA the default,并勾选 Let me override defaults





  • 点击继续,修改有效期为 7300 天,即为 20 年。





  • 下一步,选择证书。这里我选择的是自己账号的证书。



  • 接下来,一直下一步,直至创建完成。





点击 Show CA Certificate 可以看到成功创建的证书。



2. 创建叶子证书



  • 打开证书助理,选择创建证书颁发机构

  • 填入名称和邮箱,反选 Make this CA the default,并勾选 Let me override defaults



注意这里跟第一步有所不同:



  1. Identity Type 选择 intermediate CA

  2. User Certificate 选择 custom,找到第一步中创建的 xx.certAuthorityConfig 文件。其目录在 ~/Library/Application Support/Certificate Authority/<your CA name>/<your CA name>.certAuthorityConfig





  • 修改有效期,同第一步中对应的操作

  • 选择证书,同第一步中对应的操作

  • 选择发行者。

这里选择第一步中创建的证书,我的证书名是 summer





  • 一路下一步,直至完成

3. 导出叶子证书



Keychain Access 中,选中第 2 步创建的 leaf certificate ,右键导出为 cer 格式,并加入到自己的工程中。

4. 导出 CA 证书



Keychain Access 中,选中第 1 步创建的 root certificate ,右键导出为 cer 格式。不需要添加到工程。

5. 创建 Debug 描述文件



  • 下载 Apple Configurator 2

  • 点击 File -> New Profile

  • General 中填入描述文件名称。

  • Certificates 一栏中选择在第 4 步中导出的 root.cer

  • cmd+s 保存 Profile,取个合适的文件名,这里我将其命名为 custom-debug.mobileconfig

6. 编写检测代码



这里我将原文中的 SecTrustEvaluate 替换为了SecTrustEvaluateWithError,因为 SecTrustEvaluateiOS 13 上不再推荐使用。



SecTrustEvaluateWithError 的返回值为 bool。若为 true,则表示信任;若为 false,则表示不受信任。



代码如下:

- (BOOL)IsMobileConfigInstalled {
NSString certPath = [[NSBundle mainBundle] pathForResource:@"Certificates" ofType:@"cer"];
if (certPath == nil) {
return NO;
}
NSData certData = [NSData dataWithContentsOfFile:certPath];
if (certData == nil) {
return NO;
}
SecCertificateRef cert = SecCertificateCreateWithData(NULL, (bridge CFDataRef) certData);
SecPolicyRef policy = SecPolicyCreateBasicX509();
SecTrustRef trust;
OSStatus err = SecTrustCreateWithCertificates((bridge CFArrayRef) [NSArray arrayWithObject:(__bridge id)cert], policy, &trust);
NSLog(@"Error Status?: %d", err);
CFErrorRef error;
BOOL result = SecTrustEvaluateWithError(trust, &error);
NSLog(@"%d, %@", result, error);
CFRelease(trust);
CFRelease(policy);
CFRelease(cert);
return result;
}



7. 在需要调试的机器上安装描述文件



  • 将手机用 USB 连上电脑,选择对应的设备。

  • 点击添加,选择 Profiles,再选择第 5 步中创建的描述文件。





  • 此时,在手机上会出现描述文件已经下载的提示。如下图所示:





同时 Apple Configurator 是下图状态,等安装完成会自动消失。





  • 安装描述文件。打开 手机设置 -> 通用 -> 描述文件与设备管理,选择对应的描述文件进行安装。

8. 运行工程



运行工程,会发现此时 SecTrustEvaluateWithError 返回的 resultYES





另外可以自行测试一下,如果移除了描述文件 Debug Profile,该结果则为 NO



发布于: 2020 年 05 月 03 日阅读数: 172
用户头像

liu_liu

关注

不要相信自己的记忆力 2017.11.13 加入

还未添加个人简介

评论

发布
暂无评论
iOS Release 版本开启调试功能