iOS 的 Code Signing 体系
iOS中的Code Signing
体系非常复杂,对新手非常不友好,虽然目前网上已经存在大量文章对此进行比较透彻的分析,最核心的部分已经讲解得非常清楚,我阅读了这些文章后,确实从中学习到不少知识,但我始终还是对Code Signing
体系中很多相关的地方有着疑惑,于是决定认真地探究一番。本文会引用一些比较好的文章中的内容和图片,加上一些我个人的理解进行分析,有些内容本文不再重复,有需要的请阅读本文最后的参考文章。
概念
公开密钥加密、数字签名、证书这些通用的基本概念这里不再多说,主要提一下iOS
上特有的东西
.certSigningRequest
点击mac OS
的钥匙串访问
里的 证书助理 -> 从证书颁发机构请求证书
,最后会创建出一个.certSigningRequest
文件,其实这个过程就是创建了一对公私钥
- 其中
.certSigningRequest
文件保存着- 申请者信息申请者的公钥
- 摘要算法
- 公钥加密算法
- 私钥保存在
keychain
中
证书
AppleWWDRCA
iOS
以及 mac OS
(在安装 Xcode 时)将自动安装 AppleWWDRCA.cer
这个中间证书(Intermediate Certificates),它实际上就是 iOS(开发)证书的证书,即根证书(Apple Root Certificate)。
iOS App Development
iOS的开发证书,在开发阶段进行真机测试时需要用到的证书。可以在苹果开发网站上手动创建,需要上传.certSigningRequest
文件;或者使用Xcode自动创建。
iOS Distribution
iOS的发布证书,可以用于进行 Ad Hoc 测试、打包上传到 App Store 或者打包成 Enterprisee(In-House) 类型供企业内部使用。可以在苹果开发网站上手动创建,需要上传.certSigningRequest
文件;或者使用Xcode自动创建。
.p12
在mac OS
的钥匙串访问
里选择一张证书,右击该证书,选择导出"xxxxx"
,然后设置密码,可以导出该证书对应的.p12
文件。.p12
文件包含个人信息、公钥和私钥,也就是证书 + 私钥
。iOS类型的每种证书同时存在数量有限制,而证书是依靠mac OS
上的.certSigningRequest
文件创建的,所以正常情况下,每种类型的证书只能在有限的Mac电脑上使用,如果需要在更多不同的Mac电脑上进行App开发、测试、签名,可以导出对应.p12
文件代替证书来使用。
[图片上传中...(image-7e7b7d-1566972023627-8)]
<figcaption style="display: block; text-align: center; font-size: 1rem; line-height: 1.6; color: rgb(144, 144, 144); margin-top: 2px;"></figcaption>
Provisioning Profile
Provisioning Profile
的文件格式为.mobileprovision
,里面包含着
- 可以使用的证书
- App ID,由 TeamID 和 BundleID 组合而成,类似于
A1B2C3D4.com.domain.appName
形式 - 可安装该App的设备列表的UDID
- Entitlements,授权文件,列出了App可以进行哪些行为
- 以上信息的签名
在苹果开发网站上手动创建,或者使用Xcode自动创建。
.ipa
.ipa
文件是iOS上的App安装文件,其实它只是一个压缩包,等同于.zip
格式,用mac OS
自带的归档实用工具
可以直接对它解压,可以看到里面的内容
用于上传App Store的.ipa
文件
[图片上传中...(image-2fa890-1566972023627-7)]
<figcaption style="display: block; text-align: center; font-size: 1rem; line-height: 1.6; color: rgb(144, 144, 144); margin-top: 2px;"></figcaption>
App Store下载的.ipa
文件
[图片上传中...(image-8770c8-1566972023627-6)]
<figcaption style="display: block; text-align: center; font-size: 1rem; line-height: 1.6; color: rgb(144, 144, 144); margin-top: 2px;"></figcaption>
对比两种情况的.ipa
文件,可以看出它里面最主要的是Payload
文件夹,而Payload
文件夹里面放的就是该App对应的.app
文件
.app
右击.app
文件,选择显示包内容
,可以看到里面的内容
用于上传App Store的.app
文件
[图片上传中...(image-cb9b76-1566972023627-5)]
<figcaption style="display: block; text-align: center; font-size: 1rem; line-height: 1.6; color: rgb(144, 144, 144); margin-top: 2px;"></figcaption>
从App Store下载的.app
文件
[图片上传中...(image-617dbe-1566972023627-4)]
<figcaption style="display: block; text-align: center; font-size: 1rem; line-height: 1.6; color: rgb(144, 144, 144); margin-top: 2px;"></figcaption>
可以看出.app
文件主要包含三部分:
-
Mach-O
格式的二进制可执行文件,这个是一个App最重要的文件,我们编写的Objective-C
、Swift
代码都被编译在里面 - 资源文件,包括:
.bundle
文件,.framework
文件,.dylib
文件,.nib
文件,图片文件,音视频文件,字体文件等所有项目用到的文件 -
CodeResources
,签名信息 -
embedded.mobileprovision
文件,或者entitlements
文件- 对于没有上传App Store的
.app
文件,里面会包含embedded.mobileprovision
文件,没有entitlements
文件 - App Store下载的
.app
文件,里面会包含.entitlements
文件,没有embedded.mobileprovision
文件
- 对于没有上传App Store的
Code Signing
正常情况下(非越狱),所有App想要安装到iOS设备上,只有以下几种方法
- 非App Store
- 真机调试
- Ad-Hoc
- In-House
- App Store
无论是哪一种方法,都需要先把iOS项目编译成.app
文件,然后进行签名。按照惯例,需要分析一下Code Signing
非App Store
对于非App Store获得的.ipa
文件,需要严格复杂的签名和验证流程
[图片上传中...(image-eb8b6a-1566972023627-3)]
<figcaption style="display: block; text-align: center; font-size: 1rem; line-height: 1.6; color: rgb(144, 144, 144); margin-top: 2px;"></figcaption>
-
创建
.certSigningRequest
文件,这时候会生成一对公私钥,这里称为公钥L
(L:Local),私钥L
。.certSigningRequest
文件保存着公钥L
-
iOS
以及mac OS
上的AppleWWDRCA.cer
证书保存的就是苹果的公钥A
(A:Apple),而对应的私钥A
则在苹果的后台 -
在苹果开发者网站上创建证书的时候,上传
.certSigningRequest
文件其实就是把公钥L
传到苹果后台,用苹果后台里的私钥A
去签名公钥L
,得到对应的证书,把证书下载回来双击安装,会跟对应的私钥L
绑定一起保存在keychain
中 -
除了证书,还需要对应的
Provisioning Profile
。在苹果开发者网站上:- 设置App的Bundle ID
- 设置可安装该App的设备UDID
- 设置该App的权限
- 设置可以使用的证书
- 最后会使用
私钥A
把以上这些数据进行签名,组成一个Provisioning Profile
,格式为.mobileprovision
,下载回来双击安装,会保存在~/Library/MobileDevice/Provisioning Profiles
中,文件名为它的UUID
-
当需要把一个App安装在iOS设备上时,都会先把iOS项目编译打包成
.app
文件,而打包成可安装在iOS设备上的.app
文件的前提是,设置该App的Provisioning Profile
,最后使用合法的证书对源代码编译后的各种文件进行签名,步骤如下:- 用
mac OS
里的公钥A
验证Provisioning Profile
,获取里面的信息 - 用
Provisioning Profile
里面的信息验证App的Bundle ID是否对应,App的权限是否对应 - 用
公钥A
验证Provisioning Profile
里面的证书,再判断是否有其中一张证书在这台Mac电脑里 - 如果上面的验证都通过了,则会从
mac OS
的keychain
中取出符合条件且最新创建的证书,拿到对应的私钥L
- 如果有
.framework
文件、.dylib
文件、插件、watch目录下的extension,对它们分别进行签名 - 把
Provisioning Profile
改名为embedded.mobileprovision
放在.app
文件里面 - 使用
私钥L
对整个.app
文件进行签名,得到签名信息CodeResources
也放会在.app
文件里面
- 如果有
- 如果需要生成
.ipa
文件,则会把.app
文件放在Payload
文件夹里,把Payload
文件夹和一些其他信息文件(非必要),一起压缩形成一个.ipa
文件
- 用
-
把
.ipa
文件或者.app
文件安装在iOS设备上时- 先使用iOS设备上的
公钥A
对.app
文件里面的embedded.mobileprovision
文件进行验证,获取里面的证书 - 再使用
公钥A
对embedded.mobileprovision
文件里面存在的证书进行验证,取出一张对应的证书,得到公钥L
- 使用
公钥L
对.app
里面所有签名信息进行验证,如果验证通过,证明该.app
文件是完整合法,没有被篡改的 - 获取
embedded.mobileprovision
文件里面的可安装该App的设备UDID列表,判断该iOS设备是否可以安装 - 如果前面的验证都通过,则App会安装在iOS设备上
- 先使用iOS设备上的
App Store
当需要在App Store发布App时,则先需要把.ipa
文件上传到App Store。苹果会用一种非常简单的方式进行重新签名,这是因为在把.ipa
文件上传到App Store之前,会先进行类似于上面步骤的一系列验证,只有通过验证才会上传成功,所以这已经进行过一次复杂的验证,代表苹果已经认同了这个.ipa
文件,而用户又是从App Store下载的,所以也保证了.ipa
文件来源是权威的,最后只需要在用户设备上进行简单的验证就可以
[图片上传中...(image-8c5dc8-1566972023627-2)]
<figcaption style="display: block; text-align: center; font-size: 1rem; line-height: 1.6; color: rgb(144, 144, 144); margin-top: 2px;"></figcaption>
- 苹果用
私钥A
对.app
文件里面需要签名的文件进行重新签名 - 用户下载App Store上面的
.ipa
文件进行安装时,用iOS设备上的公钥A
对.app
文件里所有签名信息进行验证,如果验证通过,则会安装在iOS设备上
更多相关
以上就是Code Signing
最核心的内容,但在iOS开发中其实还有很多相关的概念,很多需要注意的地方,接下来会讲解一下我个人的观察和分析
苹果开发者帐号体系
Apple Developer:直接在Apple Developer登录,同意Apple Developer协议后的账号,免费,只可以使用Xcode进行真机调试,Xcode 7之后苹果推出的功能
Apple Developer Program:分个人和组织类型,费用都是每年 99 美元,可以使用Xcode进行真机调试,打包Ad-Hoc测试,在App Store发布App
Apple Developer Enterprise Program:企业账号,费用是每年 299 美元,可以使用Xcode进行真机调试,打包Ad-Hoc测试,打包In-House App,但不能在App Store发布App
不同安装方式对应的证书类型
- 非App Store
- Development(真机调试):iOS App Development
- Ad Hoc:iOS Distribution (App Store and Ad Hoc)
- Enterprise:iOS Distribution (In-House and Ad Hoc)
- App Store:iOS Distribution (App Store and Ad Hoc)
在iOS的项目中,只要不是运行在模拟器上,都会涉及到开发者帐号、证书、Provisioning Profile
这些概念。
免费账号的限制:
- 创建的
Provisioning Profile
有效期只有7天 - 在7天内最多注册10个Bundle Id
- 只能同时注册3台iOS设备
- 在同一台iOS设备上,只能同时安装3个使用免费账号签名的App。当该设备上已经存在3个App,则无法安装任何免费账号签名的任何App,就算是那3个App其中一个也不行,只能先把其中一个删除
Automatic signing
在Xcode 7之前,只有加入到Apple Developer Program(即付费)才能进行真机调试,Xcode 7之后苹果推出了Automatic signing
功能,只要在Xcode上登陆Apple ID,就会自动管理证书和Provisioning Profile,同时没有加入Apple Developer Program的账号也能进行真机调试。
[图片上传中...(image-47ff33-1566972023626-1)]
<figcaption style="display: block; text-align: center; font-size: 1rem; line-height: 1.6; color: rgb(144, 144, 144); margin-top: 2px;"></figcaption>
在使用Xcode的Automatic signing
功能的时候,无论是什么类型的账号,无论是直接Run
进行真机调试,还是Archive
,Code Signing Identity
只能选用iOS Developer
,只会使用iOS App Development
类型的证书签名
如果不使用Automatic signing
功能,则可以选择使用的是iOS App Development
或者iOS Distribution
证书进行签名
勾选Xcode中的AutoMatically manager signing
,选择对应的Team
后,无论是加入Apple Developer Program的账号(即付费账号)还是Apple Developer的账号(即免费账号):
-
如果Xcode没有帮该账号自动生成过
iOS App Development
类型的证书, 无论在苹果后台是否已经存在其他iOS App Development
类型的证书,都会生成一张新的iOS App Development
类型证书,证书名称的格式是:开发者账号名称(当前Mac电脑名称), 如:Brian Hui (Daniels的MacBook Pro),同时会保存在当前Mac电脑的keychain
中 -
免费账号无法进入苹果的管理证书后台,但可以猜测出在苹果后台也会存在该证书
-
如果Xcode没有帮该App的Bundle ID自动生成过对应的
Provisioning Profile
,就会使用上面那张证书生成一个Provisioning Profile
,保存在~/Library/MobileDevice/Provisioning Profiles
,但在苹果后台则不会存在这个Provisioning Profile
-
如果在
钥匙串访问
中删除了那张证书,Xcode会提示你的账号有iOS App Development
类型的证书,但这台电脑没有安装,需要先把那张证书Revoke
,Revoke
后会再次重复前面的步骤,生成新的证书和Provisioning Profile
-
如果在
~/Library/MobileDevice/Provisioning Profiles
里面,删除了该Provisioning Profile
文件,Xcode会马上重新生成Provisioning Profile
在使用Xcode的Automatic signing
功能的前提下,进行Archive
,然后Distribute App
的时候,选择非Development
的选项,再选择AutoMatically manager signing
-
如果本地存在
iOS Distribution
类型的证书,则会直接进行重签名 -
如果没有存在
iOS Distribution
类型的证书,而苹果的后台有,则会告诉你,该账号存在iOS Distribution
类型的证书,但这台电脑没有安装,请联系创建人拿到备份(.p12文件)进行安装,当你安装了该证书(或者.p12文件),则会直接进行重签名 -
如果没有存在
iOS Distribution
类型的证书,而苹果的后台也没有,则Xcode会询问你是否需要生成iOS Distribution
类型的证书,如果选择需要,则会自动生成iOS Distribution
类型的证书,并且建议你保存在本地,证书名称的格式是:Team Name, 如:Hutchison Telephone (Macau) Company Limited,同时使用这张证书生成一个Provisioning Profile
,保存在~/Library/MobileDevice/Provisioning Profiles
,但在苹果后台则不会存在这个Provisioning Profile
Xcode对Provisioning Profile的验证
Xcode怎么把App和证书、Provisioning Profile
绑定在一起呢?什么时候需要一张新的证书,什么时候需要一个新的Provisioning Profile
?
Bundle ID是App的唯一标识,App和证书、Provisioning Profile
绑定在一起,其实就是Bundle ID和证书、Provisioning Profile
绑定在一起,两种情况:
Automatic signing
Bundle ID与开发者账号绑定。使用Automatic signing
时,选择开发者账号(Team)后,Xcode会根据开发者账号去本地检索是否存在该账号对应的Provisioning Profile
,再验证是否存在与该Bundle ID匹配的Provisioning Profile
,再根据Provisioning Profile
去本地检索是否存在对应的证书,都验证通过,则会设置成功。如果不存在Provisioning Profile
,则会判断该Bundle ID是否已经被其他账号注册,如果已经被其他账号注册,则整个流程失败,需要选择对应的账号。如果该Bundle ID没有被其他账号注册或者账号已经对应上,则按照文章前面所说的步骤,最后生成Provisioning Profile
。
没有使用Automatic signing
需要手动选择Provisioning Profile
,当选择了其中一个Provisioning Profile
时,则会分别验证Bundle ID是否对应、Provisioning Profile
是否过期、是否存在对应的证书、App的权限是否对应、证书的类型和Code Signing Identity
设置是否对应,如果都通过验证,则会设置成功。
结论
当你使用一台新的Mac电脑,进行开发、调试、发布某个开发者账号注册的App时,就需要一张新的证书(或者.p12文件);当你使用新的证书或者新的App,就需要一张新的Provisioning Profile
。一张证书对应一台Mac电脑,和一个开发者账号里面所有App,而一个Provisioning Profile
则是只能对应一个App。
检验Provisioning Profile
是签名的第一步,那如果Provisioning Profile
通过检验,而Provisioning Profile
里面又存在证书,为什么还需要单独安装证书呢?
答案是:Provisioning Profile
里面证书的作用是验证本地是否有符合条件证书,并且在安装App的时候使用其中一张证书里面的公钥L
来验证App的完整性和合法性。因为Provisioning Profile
里面是有多张证书的,所以无法确定用哪张证书对应的私钥L
用来签名,所以这些证书只能用于判断Mac电脑里有没有符合条件的证书,如果Mac电脑里有多张符合条件的证书,则默认用其中最新的证书里面的私钥L
进行签名,这样就可以限制了只有获得符合条件的证书的Mac电脑,才能进行签名。
Mac电脑中证书的作用:1. 证明这台电脑是合法的;2. 找到对应的私钥L
来对.app
文件进行签名
Provisioning Profile
中证书的作用:1. 判断这台电脑是否可以进行签名,也就是判断这个台电脑的合法性;2. 安装App时检验.app
文件
Xcode的Build Configuration和Code Signing Identity
Xcode有Run
、Archive
、Test
等几种项目构建方式,每一种可以指定不同的Build Configuration
(默认有Debug
和Release
),而在Code Signing Identity
又需要指定每种Build Configuration
对应的配置,分别为iOS Developer
和iOS Distribution
,其实就是在设置Provisioning Profile
的类型是开发的还是发布的
[图片上传中...(image-b58def-1566972023626-0)]
<figcaption style="display: block; text-align: center; font-size: 1rem; line-height: 1.6; color: rgb(144, 144, 144); margin-top: 2px;"></figcaption>
在使用Xcode的Automatic signing
功能的时候,Code Signing Identity
只能选择iOS Developer
,否则会报错
Xcode的Run、Archive和Distribute App
Run
和Archive
的时候已经对App完成签名。如果设置的Provisioning Profile
不是用于发布到App Store的,这时候把里面的.app
文件打包成.ipa
文件,可以利用Xcode或者其他工具直接安装到指定的iOS设备上。而点击Archives
界面中Distribute App
后,会再给你一次机会,选择其他证书和Provisioning Profile
进行重签名,用于将App发布到不同的渠道
iOS设备上打开App时的验证
把App安装到iOS设备时,需要经过本文前面所说的验证,而打开App的时候,也需要验证:
-
使用付费账号生成的
Development
和AD-Hoc
类型的.ipa
文件,安装后打开对应的App时,不需要在iOS设备上进行信任开发者操作,每次打开都会验证证书和Provisioning Profile是否被Revoke
或者过期,如果验证不通过,则无法打开 -
使用免费账号生成的
Development
和企业账号生成的Enterprise
类型的.ipa
文件,安装后第一次打开App时,需要在iOS设备上做额外的信任开发者操作,每次打开都会验证证书和Provisioning Profile是否被Revoke
或者过期,如果验证不通过,则无法打开 -
App Store下载的
.ipa
文件,安装后第一次打开App时,会验证当前登录的App ID是否已经购买该App,如果验证通过,则以后都可以正常打开,如果验证不通过则无法打开
重签名
在完全了解iOS中的Code Signing
体系后,除了可以让你在日常开发中遇到证书、签名等问题的时候解决起来得心应手,还有一个重要的应用就是重签名。网上也有一大堆关于重签名的文章,大多数都只是说了怎么操作,但是很少会解释为什么要这样操作。接下来我会结合本文前面的内容详细分析重签名的原理。
原理
-
重签名顾名思义,就是把
.ipa
文件也就是.app
文件进行重新签名。经过前面的分析,iOS的Code Signing
体系是依靠两个文件来进行签名:证书和Provisioning Profile
,它们是由苹果后台生成的,并且用私钥A
进行签名,所以重签名的第一步就是需要准备有效合法、由苹果后台生成的证书和Provisioning Profile
,它们必须是相对应并且没有被修改过的。需要注意的是,如果这证书不是由你的Mac电脑去请求生成的,是它无法跟它里面公钥L
对应的私钥L
绑定在一起,因为你的Mac电脑里根本没有对应的私钥L
,所以这时候是需要它对应的.p12
文件,安装.p12
文件就会得到证书和对应的私钥L
-
签名的时候,会拿到
Provisioning Profile
里面的信息验证App的Bundle ID是否对应,App的权限是否对应,所以要把进行重签名的App的Bundle ID改成跟Provisioning Profile
记录的一致。而App的权限则需要直接从Provisioning Profile
中导出entitlements.plist
文件,最后在重签名的时候使用 -
既然是对
.app
文件进行重签名,那么最后就是需要准备一个没有被加密的.app
文件。在App Store下载.ipa
文件里面的.app
文件都是被加密,被加密的.app
文件无法进行重签名 -
准备工作已经完成,现在就可以开始重签名,步骤其实是跟初次签名一样的。首先把准备好的
Provisioning Profile
改名为embedded.mobileprovision
放在.app
文件中或者进行覆盖。使用准备好的证书,对.framework
文件、.dylib
文件、插件、watch目录下的extension分别进行签名,最后用证书和导出entitlements.plist
文件再对整个.app
文件签名 -
关于证书的类型和App的安装限制:既然进行了重签名,那么这个App可以安装的iOS设备就会受到使用的证书和
Provisioning Profile
的限制,所以一般会使用企业账号的发布证书和In House类型Provisioning Profile
进行重签名,这样就可以使App安装在任何iOS设备上
步骤
- 准备合法完整的证书(或者.p12文件)和
Provisioning Profile
- 准备一个已经脱壳的
.app
或者.ipa
文件,其中Bundle ID要跟Provisioning Profile
中的一致
// 在.app文件中,把Info.plist的Bundle ID改为 com.xxx.xxx
/usr/libexec/PlistBuddy -c 'Set :CFBundleIdentifier com.xxx.xxx' "Info.plist"
复制代码
- 从
Provisioning Profile
提取entitlements
// 从embedded.mobileprovision文件中提取出entitlements.plist权限文件
security cms -D -i embedded.mobileprovision > temp.plist
/usr/libexec/PlistBuddy -x -c 'Print :Entitlements' temp.plist > entitlements.plist
复制代码
- 把
Provisioning Profile
改名为embedded.mobileprovision
放在.app
文件中 - 使用准备好的证书,对
.framework
文件、.dylib
文件、PlugIns目录里的.appex
文件、Watch目录里的.app
文件分别进行签名,最后用证书和导出entitlements.plist
文件再对整个.app
文件签名
// 查看可用的证书
security find-identity -v -p codesigning
// 对.app内部的.framework文件、.dylib文件、PlugIns目录里的.appex文件、Watch目录里的.app文件分别进行签名
codesign -fs 证书ID xxx.dylib
// 对.app文件进行签名
codesign -fs 证书ID --entitlements entitlements.plist xxx.app
复制代码
- 查看新的签名信息
codesign -d -vv xxx.app
复制代码
- 打包成
.ipa
文件
zip -r xxx.ipa Payload/
复制代码
注意
- 正常情况下
.app
的Bundle ID要跟Provisioning Profile
中的一致,但实际操作发现,就算Bundle ID不一致也可以正常安装使用,但应该存在一定的隐患,所以建议还是保持一致 - 重签名的时候,为了方便,可以直接把
.app
文件里面的PlugIns目录、Watch目录都删除,一般情况下不会用到。但是为了项目的完整性,以防出错,最好把它们也保留。PlugIns目录的文件可以直接用证书分别对它们进行签名。而在微信里,Watch目录里面是一个.app
文件,我不确定是不是所有App都是如此,以微信为例,对它进行签名前,需要进去打开内部的Info.plist
文件,把其中两个值进行修改,分别是:- WKCompanionAppBundleIdentifier,这个key对应的值指定了Watch目录的
.app
文件用于哪个App,在微信中为com.tencent.xin
,可以看出是跟微信的Bundle ID一样,所以需要把它改为跟Provisioning Profile
中的一致 - Bundle identifier,这个key对应的值表示Watch目录的
.app
文件自己的Bundle ID,在微信中为com.tencent.xin.watchapp
,可以看出它的前缀是微信的Bundle ID,所以需要把它的前缀改为跟Provisioning Profile
中的一致,后面的部分保持不变就可以 - 完成上面的修改后就可以用证书对Watch目录里面的
.app
文件签名
- WKCompanionAppBundleIdentifier,这个key对应的值指定了Watch目录的