日常的iOS 开发中,总是会遇到各种与证书、签名相关的问题,总是被这些问题整的焦头烂额,那么这篇我们就仔细分析一下证书幕后的工作原理。
基本概述
iOS 开发中的各种证书的核心就是非对称加密技术(也就是我们常说的公钥/私钥加密技术)。为了深入了解证书幕后的工作原理,我们先引入两个概念:
- 数字签名
- 数字证书
数字签名
数字签名(Digital Signature)是一种类似于盖章、签字的功能在数字信息领域的实现。数字签名可以识别篡改和伪装。在数字签名技术中,有两种行为:签名生成和签名验证。
签名生成
签名生成由通信中的发起方进行,过程如下图所示。首先对通信内容进行哈希,然后使用发送方的私钥进行加密,最终得到签名。
签名验证
签名验证由通信中的接收方进行,过程如下图所示。一般来说,发送方会把消息、签名一起发送给接收方。接收方首先使用发送方的公钥对签名进行解密,计算得出一个摘要。然后使用消息进行哈希,计算得出另一个摘要。最后,判断两个摘要是否相等,如果相等则说明接收到的消息没有被第三方进行篡改。
那么接收方是如何获取到发送方的公钥的呢?接收方又是如何确定该公钥就是属于发送方的呢?这就是数字证书要做的事。
数字证书
数字证书(Digital Certificate)是一种类似现实中身份证的功能在数字信息领域中的具体实现。数字证书包含了个人或者机构的身份信息及其公钥,因此也称为公钥证书(Public-Key Certificate,PKC)。
类似于身份证是有权威的公安局颁发的,公钥证书也是由权威的认证机构(Certificate Authority,简称CA)颁发的。认证机构向接收方提供发送方的身份信息和公钥。为了防止证书在颁发过程中被篡改,认证机构会将身份信息和公钥作为消息,用CA私钥 进行签名,进而将身份信息、公钥、签名一起放入证书,过程如下所示:
根证书
接收方得到发送方的证书时,通过CA公钥对证书进行签名验证。
需要注意的是,在很多情况下,CA公钥是由一个更加权威的机构颁发的。类似于一个上级的权威机构。证书是具有信任链的(Chain of Trust),根证书(Root Certificate)是信任源,也就是信任链的起源。根证书的颁发者被称为Root Certificate Authority (简称 Root CA)。某一认证领域内的根证书是Root CA 自行颁发给自己的证书(self-signed Certificate),安装证书即对这个CA认证中心的信任。
根证书在信任链中的位置,可以将证书分为三种:
- 根证书(Root Certificate)
- 中间证书(Intermediate Certificate)
- 叶子证书(Leaf Certificate)
根证书都是随软件一起安装的,比如:操作系统安装时会内置一份可信的根证书列表。
iOS证书
上面介绍完了数字签名(签名生成、签名验证)和数字证书的基本概念之后,我们来说一下iOS 开发中的相关证书。
首先,我们来看一下 MacOS 系统中关于 iOS 开发证书的信任链示例(通过“钥匙串”查看):
-
Apple Root Certificate Authority:根证书
-
-
iPhone Developer: (XXXXXXX) :叶子证书
-
iPhone Distribution: Apple Tech:叶子证书
-
Apple Development: xxxx@apple.com:叶子证书
-
Apple Worldwide Developer Relations Certification Authority:中间证书
-
-
根证书 Apple Root Certificate Authority 是在Mac OS 操作系统安装时内置的,是Apple Root CA自行颁发的。中间证书 Apple World Developer Relations Certificate Authority (AppleWWDRCA.cer)是在Xcode 安装时内置的,是Apple Root CA 颁发的。虽然AppleWWDRCA.cer 是中间证书,但是对于iOS 开发来说,它就是开发根证书。我们开发所使用的证书都是叶子证书,是Apple Worldwide Developer Relations Certification Authority 颁发的。下面我们介绍一下是如何申请开发证书的?
-
申请原理
-
下图所示,是证书申请的基本原理,可分为以下几个步骤:
-
- 开发者在本地生成密钥对,并提供开发者的身份信息。
- 将密钥对中的公钥、身份信息发送给 CA。
- CA 使用 CA 私钥对开发者的公钥、身份信息进行签名。
- CA 将开发者的公钥、身份信息、签名组装成证书以供下载。
-
-
申请方法
-
上面介绍了iOS开发证书的申请原理。在iOS开发中,一般有两种申请方法:(1)CertificateSigningRequest (2)Xcode 自动申请
-
CertificateSigningRequest
-
首先通过“钥匙串” 菜单栏选择【从证书颁发机构请求证书】。
-
-
其次,填写用户的身份信息(电子邮箱),并勾选【存储到磁盘】。点击【继续】后将会保存一个CSR (CertificateSigningRequest.certSigningRequest)文件到本地。另外,这个期间会生成一堆非对称密钥对,CeertificateSigningRequest.cerSigningRequest 本质上包含了开发者信息和公钥。私钥则始终保存在开发者的Mac中。
-
-
然后我们就可以在开发者网站上传CSR文件,由CA 进行签名并生成开发者证书。开发者证书始终保留在开发者网站上,开发者可以删除(Revoke)已注册的证书。
-
-
最后从开发者网站下载开发者证书后,双击即可安装。
-
Xcode 自动申请
-
通过,Xcode 菜单【Preference…】->【Account】->【Apple IDs】->【+】,登录开发者账号。
-
-
登录成功之后,“钥匙串”会自动导入一份证书(包含一份密钥对)。开发者网站也会注册一份证书。这种方式属于一键式申请,方便快捷,推荐使用。
-
-
使用
-
iOS证书包含开发者的信息以及开发者的公钥。Xcode 导入证书后,对App打包时,Xcode 会根据证书从keychain 中找到与之匹配的私钥,并使用私钥对App进行签名。当App 安装到真机时,真机使用开发者的公钥对App进行签名验证(App 中包含开发者公钥),从而确保来源的可信。
-
-
分类
-
iOS证书分为两种:
-
-
Development :开发证书,用来开发和调试App。一般证书名称是iPhone Developer 。如果是多人协作的开发者账号,任意成员都可以申请自己的Development 证书。
-
Distribution:发布证书,用来发布App。一般证书名称是iPhone Distribution。多人协作时,只有管理员以上的身份的开发者账号才可以申请,所以是可以控制提交权限的。
下面我们主要针对iOS App开发过程中的开发证书进行介绍。
授权文件(Entitlements)
沙盒(Sandbox)技术是iOS 安全体系中非常重要的一项技术,它的目的是限制App的行为,比如:刻度写的路径、允许访问的硬件、允许使用的服务等等。因此,如果代码出现bug,也不会影响沙盒外的系统。
沙盒使用授权文件(Entitlements)声明App的权限。如果App中使用到了某项沙盒限制的功能,但是没有声明对应的权限,运行到相关代码时会crash。另外,新建的工程是没有 Entitlements 文件的,如果在【Capabilities】中开启所需要使用的权限后,Xcode 会自动生成 Entitlements 文件,并将对应的权限声明添加到该文件中。
Entitlements 文件是一个 xml 格式的 plist 文件,在项目中一般以 .entitlements 为后缀,其内容如下:
|
aps-environment production com.apple.developer.associated-domains applinks:xxxx.companyname.com com.apple.security.application-groups group.com.companyname1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16| ```| ----------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 事实上,Entitlements 文件的内容并不是全部的授权声明。因为缺省状态下,App 默认会包含与 TeamID 及 App ID 相关的权限声明,如下: | ``` 1 2 3 4 5 6 7 8 9 10 11 12 ``` | ``` <dict> <key>keychain-access-groups</key> <array> <string>xxxxxxxxxx.*</string> </array> <key>get-task-allow</key> <true/> <key>application-identifier</key> <string>xxxxxxxxxx.test.CodeSign</string> <key>com.apple.developer.team-identifier</key> <string>xxxxxxxxxx</string> </dict> ``` | | ----------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 其中 get-task-allow 代表是否允许被调试,它在开发阶段是必需的一项权限,而在进行Archive打包用于上架时会被去掉。 注意:代码签名时,会将Entitlements 文件与上述缺省内容进行合并,得到最终的授权文件,并嵌入二进制代码中,作为被签名内容的一部分,由代码签名保证它的不可篡改性。 **App ID** App ID 即Product ID ,用来标识一个或者一组App。App ID 字符串通常以反域名格式的Company Identifier(Company ID)作为前缀,一般不超过255个ASCII 字符。App ID 全名会被追加 Application Identifier Prefix(一般为 TeamID)。App ID 可以分为两种: - Explicit App ID :唯一的App ID ,用于标识一个应用程序。如:com.apple.garageband 用于标识 Bundle Identifier 为 com.apple.garageband 的 App。 - Wildcard App ID:含有通配符的 App ID,用于标识一组应用程序。如:com.apple.* 用于标识 Bundle Identifier 以 com.apple. 开头(苹果公司)的所有应用程序。 开发者可以在Developer Member Center 网站上注册或者删除已注册的App IDs。在Xcode 中,配置项 Xcode Target -> Info -> Bunlde Identifier 必须与 App ID 是一致的(Explicit)或匹配的(Wildcard)。注册App ID 时,允许开发者在【Capabilities】中勾选所需的权限。与前文提到的授权文件Entitlements 相匹配。  # 设备 设备(Device)即用于开发调试的iOS 设备。每台Apple 设备谁用UDID(Unique Device Identifier)作为唯一标识,也就是设别ID。Apple Member Center 网站个人账号下的Device 中包含注册过的所有可用于开发和测试的设备,普通的个人开发者账号每年累计最多注册100台设备。开发者可在网站上注册或者启用/禁用(Enable/Disable)添加的设备。iOS 中指的是连接到Xcode 被授权用于开发测试的设备(iPhone、iPad)。 描述文件(Provisioning Profile) 创建 描述文件(Provisioning Profile,简称 pp文件)包含了所有内容: - App ID(App ID 在注册时可声明所需沙盒权限,所以包含了 Entitlements) - 证书 - 设备ID  一个Provisioning Profile 对应一个Explicit ID或者 Wildcard ID。在开发者网站上手动创建一个描述文件时,需要确定以上三项内容: (1)App ID:单选(沙盒权限时可多选) (2)证书:可多选,对应多个开发者 (3)设备:可多选,对应多个开发设备 开发者可以下载描述文件,即一个 .mobileprovision 文件。开发者在权限允许的范围内也可以删除描述文件。 Provisioning Profile 会配置到 Xcode -> Target -> Signing & Capabilities -> Provisioning Profile 中。 Provisioning Profile 默认保存在本地的 ~/Library/MobileDevice/Provisioning Profiles 目录下。 构成 .mobileprovision 包含以下这些字段及内容: - Name:即 mobileprovision 文件。 - UUID:即 mobileprivision 文件的真实文件名,是一个唯一标识。 - TeamName:即 Apple ID 账号名。 - TeamIdentifier:即 Team Identity。 - AppIDName:即 explicit/wildcard Apple ID name(ApplicationIdentifierPrefix)。 - ApplicationIdentifierPrefix:即完整 App ID 的前缀。 - ProvisionedDevices:该 .mobileprovision 授权的所有开发设备的 UUID。 - DeveloperCertificates:该 .mobileprovision 允许对应用程序进行签名的所用证书,不同证书对应不同的开发者。如果使用不在这个列表中的证书进行签名,会出现 code signing failed 相关报错。 - Entitlements:包含了一组键值对。<key>、<dict>。 - - keychain-access-groups:$(AppIdentifierPrefix) - application-identifier:带前缀的全名。如:$(AppIdentifierPrefix)com.apple.garageband - com.apple.security.application-groups:App Group ID。 - com.apple.developer.team-identifier:同 Team Identifier。 ## 签名 & 打包 首先,Xcode 会检查 Signing(entitlement、certificate)配置是否与 Provisioning Profile 相匹配,否则编译会报错。  其次,Xcode 会检查 Signing & Capabilities 配置的证书是否在本机 Keychain Access 中存在对应的 Public/Private Key Pair,否则编译会报错。然后,Xcode 证书在本机 Keychain Access 匹配的 Key Pair 的私钥对应用程序 内容(Executable Code,resources such as images and nib files are not signed) 进行签名(CodeSign)。注意:Entitlements 文件也会被嵌入到内容中进行签名。最终,签名、Provisioning Profile、应用程序都会被打包到 .ipa 中。  **.ipa 文件** 我们可以用 file 命令来查看 .ipa 文件,从输出结果可以看出它是一个压缩文件。对 .ipa 文件解压后会得到一个 Payload 文件,里面包含了 .app 目录。 | ``` 1 2 ``` | ``` $ file Solar_200319-1858_r372ce72fe.ipa Solar_200319-1858_r372ce72fe.ipa: Zip archive data, at least v1.0 to extract ``` | | ------------ | ----------------------------------------------------------------------------------------------------------------------------- | ** ** **.app 文件** 以 Digital.app 为例,.app 目录下主要有以下这些类型的文件: - 可执行文件:以项目名称命名的可执行文件。如:Digital。 - xxx.bundle:资源文件,对应不同的 SDK 和 Pod。 - xxx.lproj:多语言本地化资源文件。每种语言单独定义其资源,包含:图片、文本、Storyboard、Xib 等。 - Frameworks:包含了 app 使用的第三方静态库、Swift 动态库。 - Info.plist:app 的相关配置,包括:Bundle Identifier、可执行文件名等。 - embedded.mobileprovision:描述文件(Provisioning Profile)。 - _CodeSignature/CodeResources:一个 plist 文件,保存签名时每个文件的哈希值(摘要),这些哈希值并不需要都进行加密,因为非对称加密的性能是比较差的,全部都加密只会拖慢签名和校验的速度。 验证 & 运行 在真机上运行测试包和正式包时,系统对两者的验证是不一样的。简单来说就是,测试包在设备上进行了完整的签名验证;正式包则把验证的过程交给了App Store,App Store 验证通过后重新验证一次签名,设备下载正式包后进行的验证过程就简化了很多。 下面我们说一下测试包的验证过程。 **测试包** 当在设备上运行时,会对App进行验证。首先,设备系统会对App中的bundle ID、Entitlements、certificate、Provisioning Profile 中的App ID、entitlements、certificates 进行匹配验证,否则无法启动App。其次,设备系统使用本地内置的CA 公钥对Provisioning Profile 中匹配的certificate 进行签名验证,从而确认匹配到的证书是合法的。然后,设备系统使用Provisioning Profile 中匹配的并且经过CA验证过的certificate (即打包时的开发者证书)中取出公钥,对App 进行签名验证,否则无法启动App。最后,设备系统会将设备的Device ID 与 Provisioning Profile 中的Device ID 进行匹配,否则无法启动App。  **正式包** 假如你有一台越狱的手机,查看任意一个从App Store 上下载的APP,会发现里面没有embedded.mobileprovision 文件,这是因为App Store 已经完成了对App的验证(类似于对测试包的验证过程)。当App 通过验证后,App Store 会对App 进行重新签名。重新签名的内容将不再包含 Provisioning Profile,最终的 ipa 文件也不包含它。整个过程如下图所示:  当设备从App Store下载App时,会直接使用设别上的CA公钥对ipa 进行签名验证。同上述测试包签名验证相比,正式包的签名简化了很多过程,其中一部分工作已经由App Store 完成了。 
-