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
文件代替证书来使用。
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
文件
App Store下载的.ipa
文件
对比两种情况的.ipa
文件,可以看出它里面最主要的是Payload
文件夹,而Payload
文件夹里面放的就是该App对应的.app
文件
.app
右击.app
文件,选择显示包内容
,可以看到里面的内容
用于上传App Store的.app
文件
从App Store下载的.app
文件
可以看出.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
文件,需要严格复杂的签名和验证流程
-
创建
.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
文件来源是权威的,最后只需要在用户设备上进行简单的验证就可以
- 苹果用
私钥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的账号也能进行真机调试。
在使用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
的类型是开发的还是发布的
在使用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目录的
结语
以上就是我个人对Code Signing
体系的观察和分析,大家可以看到相关的东西很多,需要注意的地方也很多,我尽量地把这些细节都总结出来,而这方面官方文档确实没有写得很详尽(或许是我没找到),所以很难把所有特性和表现都完全正确地总结出来,如果大家有发现文章中什么问题,请在评论中指出,我会及时验证、修改。
参考文章
iOS Provisioning Profile(Certificate)与Code Signing详解
网友评论