iOS Release 版本开启调试功能

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

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

在读过 How to Enable Custom Debugging in Release Builds 这篇文章后,发现一个比较新奇的方法。

常规方式

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

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

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

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

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

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

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

image.png

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

  1. 使用秘密的 url scheme

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

    • 为了防止被破解,url 不能太简单,而且开发者需要记住该 url,调试不便。
    • 由于需要在浏览器中输入 url,那么意味着需要离开 app 进行操作,增加调试成本。
    • 如果有其他人意外知道了 url,同样也可以调试。

重头戏

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

这种方式有如下好处:

  • 如果想要调试就必须有描述文件,而普通用户无法获取。
  • 只需安装一次描述文件。
  • 一个描述文件 可以被多个 app 使用,不同优先级的功能可以使用不同的描述文件。
  • App Store 安装的 app 也可以使用,因为只需要有描述文件。
  • 不需要特殊的环境和用户。

但是也有其坏处:

  • 需要创建存储证书、 描述文件。
  • 需要根据检查证书信任结果来确定是否安装了描述文件,并且作者承认,检查方式有一点 hack
  • 需要将描述文件存起来,不能丢失。

原理

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

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

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

  • 创建证书
  • 创建和安装描述文件
  • 检查证书是否被信任

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

1. 创建证书

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

image.png

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

image.png

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

image.png

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

image.png

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

image.png

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

image.png

2. 创建叶子证书

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

b. 填入名称和邮箱,反选 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

image.png

c. 修改有效期,同第一步中的 c
d. 选择证书,同第一步中的 d
e. 选择发行者。

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

image.png

f. 一路下一步,直至完成。

3. 导出叶子证书

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

4. 导出 CA 证书

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

5. 创建 Debug 描述文件

  • 下载 Apple Configurator 2
  • 点击 File -> New Profile
    a. 在 General 中填入描述文件名称。
    b. 在 Certificates 一栏中选择在第 4 步中导出的 root.cer
    c. cmd+s 保存 Profile,取个合适的文件名,这里我将其命名为 custom-debug.mobileconfig
image.png

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 步中创建的描述文件。
image.png
  • 此时,在手机上会出现描述文件已经下载的提示。
image.png

同时 Apple Configurator 是这个状态,等安装完成会消失。

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

8. 运行工程

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

image.png

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,602评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,442评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,878评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,306评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,330评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,071评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,382评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,006评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,512评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,965评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,094评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,732评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,283评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,286评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,512评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,536评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,828评论 2 345