iOS 证书相关问题

185 阅读13分钟

日常的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 为后缀,其内容如下:

      | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | ```

      aps-environment production com.apple.developer.associated-domains applinks:xxxx.companyname.com com.apple.security.application-groups group.com.companyname
      | ----------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
      
      事实上,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 相匹配。
      
      ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ec3a5a77ed514e76ae1f15395eea2429~tplv-k3u1fbpfcp-zoom-1.image)
      
        
      
      
      # 设备
      
      设备(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
      
      ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/0a2e1a29d30e418a99f837ad7c4ac8cf~tplv-k3u1fbpfcp-zoom-1.image)
      
      一个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 相匹配,否则编译会报错。
      
      ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ee821b6aa588407bbe7180ee552ee96d~tplv-k3u1fbpfcp-zoom-1.image)
      
      其次,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 中。  
      
      
      ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4877631f1f314fb99f81dec42715c224~tplv-k3u1fbpfcp-zoom-1.image)
      
      **.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。
      
      ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/aae2ce9ae44b467a946d2916994c0aea~tplv-k3u1fbpfcp-zoom-1.image)  
      
      
      **正式包**  
      
      
      假如你有一台越狱的手机,查看任意一个从App Store 上下载的APP,会发现里面没有embedded.mobileprovision 文件,这是因为App Store 已经完成了对App的验证(类似于对测试包的验证过程)。当App 通过验证后,App Store 会对App 进行重新签名。重新签名的内容将不再包含 Provisioning Profile,最终的 ipa 文件也不包含它。整个过程如下图所示:
      
      ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/3e0ed321fd614d46950d387027468ad2~tplv-k3u1fbpfcp-zoom-1.image)
      
      当设备从App Store下载App时,会直接使用设别上的CA公钥对ipa 进行签名验证。同上述测试包签名验证相比,正式包的签名简化了很多过程,其中一部分工作已经由App Store 完成了。  
      
      
      ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/0a161533d8874e43892986c7b1322485~tplv-k3u1fbpfcp-zoom-1.image)