iOS证书介绍

| /

名词解释

  • 数字签名

    • 代表一个特定的主体(签名者)对特定内容(被签名数据)的署名和认可
    • 签名的本质是用于验证数据的合法性,确保被签名的数据来自特定的来源,并且未经篡改
      • 基于非对称加密哈希算法
      • 非对称加密
        • 最常见的公钥加密算法是RSA公钥加密算法
        • 公钥 + 私钥
        • 私钥加密 - 公钥解密(反正亦可,不过一般设定其中一个作为公开的公钥)
      • 哈希算法
        • 散列、摘要算法
        • 对一段任意长度的数据,通过一定的映射和计算,得到一个固定长度的值,这个值就被称为这段数据的哈希值(hash)。
        • 哈希值不同的两段数据绝对不同 & 相同的数据计算出的哈希值绝对相同
        • 存在碰撞(不同数据段哈希值相同)
        • 哈希算法是单向算法,无法通过哈希值,计算出原始数据。
        • 常见的哈希算法有: md5, sha1, sha256等,其中sha1长度为160bits,而sha256长度为256bits
    • 综上,常见的签名算法:
      • sha1WithRSAEncryption:先对数据计算sha1摘要,再对摘要进行RSA加密
      • sha256WithRSAEncryption:先对数据计算sha256摘要,再对摘要进行RSA加密
      • md5WithRSAEncryption:先对数据计算MD5摘要,再对摘要进行RSA加密
      • 总之一般都是先hash,再RSA
      • 后续将最终结果统称为加密哈希
  • 证书

    • 一般会把公钥当做签名(数据签名)的一部分,随着数据一起分发(避免接收方需事先保存各种公钥)
    • 为确保公钥可信,把公钥公钥所有者的信息保存在一个文件里,并让一个可信的第三者使用其私钥对这个文件进行签名(证书签名),得到一个签了名的公钥文件,这个文件就叫做证书。(+外加签发者信息)
      • 证书作为签名的一部分,随着数据一起分发。
      • 若只含 加密哈希(+签发者信息), 不含 公钥(签发者证书) ,一般称为 简单签名
    • 这个可信的第三者就被称为证书颁发机构(Certification Authority),简称CA
    • CA的证书可能也是由其他更高一级的CA进行签发,形成多级证书链,系统中只需保存最高级CA的证书,中间CA的证书和信息提供者的证书依次进行递归校验即可。
      • 而证书链上的根证书颁发机构(根CA)的证书,是自签名的,被内置在设备中,设备无条件信任
      • 根证书(公钥)则由本地持有(不随数据)。
    • 理一理
      • 数据
      • 数据 + 加密哈希(数据)
      • 数据 + 加密哈希(数据) + 公钥
      • 数据 + 加密哈希(数据) + 公钥 + 证书(=公钥&公钥所有者信息 + 加密哈希(使用签发者公钥可解) + 签发者信息)
      • 其中
        • 加密哈希(签发者公钥) + 签发者信息 => 公钥信息的签名
        • 加密哈希(数据) + 公钥 + 证书 => 完整的数据签名
    • 如果涉及多级证书
      • 数据 + 加密哈希(数据) + 公钥 + 证书0(=公钥&公钥所有者信息 + 加密哈希(使用CA1的公钥可解) + CA1的信息 + CA1的证书 (CA2签名) )
        • CA1证书=CA1公钥&CA1信息 + 加密哈希(使用CA2的公钥可解) + CA2的信息 + CA2的证书(CA3签名)
        • CA(n-1)证书=CA(n-1)公钥&CA(n-1)信息 + 加密哈希(使用CA root的公钥可解) + CA root 信息 ( 此时不需要 + CA root 证书
      • 系统会检查 CA root 的信息,在本地 keychain 是否有对应的证书(且被信任),并校验即可
        • 一般情况下, CA root 证书(自签名) 不需要打到证书链
        • CA root 证书=CA root公钥&CA root信息 + 加密哈希(使用CA root的公钥可解) + CA root 信息
          • 自签名,内置于设备,无条件信任
    • 通常说使用证书进行签名
      • 实际指使用与证书所匹配的私钥进行签名,证书作为签名数据的一部分被嵌入签名中。
      • 若Keychain中只有证书,没有对应的私钥文件,则无法进行签名,会得到Missing private key之类的报错。
  • 开发者证书

    • 一般来说 iOS app 必须被苹果签名之后才能安装到设备上
    • 为方便开发过程中的频繁安装(避免每次都要交给苹果签名)
    • 开发者自己持有一套私钥证书
    • 由Apple对开发者的身份进行背书,让设备间能够接信任 开发者自行签名的app
      • 背书的方式即 Provisioning Profile
    • 普通开发者($99/year)
      • 最大测试uuid支持数:100
      • 按类别分位个人开发者账号和公司开发者账号
        • 个人: 协作人数1人
        • 公司: 协作人数多人,设置多个Apple ID,分多种管理级别的权限,申请时需要额外信息。
      • 按功能分位Development证书Distribution证书
      • Development证书
        • 开发及测试阶段使用,一般命名 iPhone Developer: xxxxxxx
        • 用于在设备安装上开发阶段的App后对App的完整性进行校验
        • 如果是多人协作的开发者账号,任意成员都可以申请自己的Development证书。(一般没必要)
      • Distribution证书
        • 用于提交AppStore的证书,一般命名 iPhone Distribution: xxxxxxxxx
        • 用于让AppStore校验提交上来的App的完整性
        • 只有管理员以上身份的开发者账号才可以申请,不能用于开发及调试
    • 企业级开发者($299 / year)
      • 企业级开发者证书
      • 签名的App可以被直接安装在任意的iOS设备上,只要用户主动信任该证书
      • 方便企业给内部员工分发生产力工具
      • 不能发布到 App Store
    • 教育机构:免费、额外申请、不可发布 App Store、 …
  • CSR 文件

    • .certSigningRequest 后缀
    • Certificate Signing Request
    • 用于向苹果请求证书
    • 包含
      • 个人信息:账号信息
      • 公钥
      • 自签名:使用自己的私钥进行签名
  • .cer证书

    • 苹果返回,双击导入 keychain, 与已有公钥、私钥自动配对
    • 签发者:Apple Worldwide Developer Relations Certification Authority
      • 中级证书颁发机构(中级CA),其证书由 Apple Root CA 根证书颁发机构(根CA) 进行签发
      • Apple Root CA 的证书,自签名的,被内置在设备中
    • 包含
      • 公钥
      • 公钥所有者信息
        • 并非由我们自行指定,而是签发者Apple根据我们的账号信息自动生成
      • 苹果的签名
  • .p12证书

    • 个人信息交换文件
    • Xcode 签名需要
    • APNs 需要
    • 包含
      • 私钥
      • 证书(含公钥
  • 推送证书

    • 用于和APNs通信的证书
    • 基本同开发者证书
    • 服务端使用推送证书(推送证书的 .p12文件)与APNs通信
      • 每个App 需要单独配置。推送证书的有限期为1年(生产的推送证书比开发的推送证书多一个月),过期之后需要重新配置。
      • 开发证书生产证书
        • 开发证书 只能用于往开发环境(sandbox)推送消息
        • 生产证书 现在可以同时支持开发环境(sandbox)和 生产环境,(以前貌似只能往开发环境推送消息)
          • 服务端和APNs通信时使用不同的参数区分开发环境(sandbox)和 生产环境
  • Entitlements

    • iOS沙盒的配置文件,声明了app所需的权限,如果app中使用到了某项沙盒限制的功能,但没有声明对应的权限,可能运行到相关的代码时会直接Crash。
    • 全新的iOS工程中是没有这个文件的,如果在Capabilities中开启了一些需要权限的功能之后,Xcode会自动(Xcode 8及之后的版本)生成Entilements文件,并将对应的权限声明添加到Entitlements文件中。
    • Capabilities 主要能力如下
      • 获取 wifi 信息
      • 使用 apple pay
      • keychain sharing
      • push notifications
      • siri 等等
    • info.plist隐私权限配置不同
      • 相册读写、打开相机、访问日历、访问通讯录等等。
      • 一般情况下,如果没有配,但代码调用了相应的API,提交二进制包就会通不过,苹果会发邮件/通知告诉你缺了配置,所以不太担心上线后 crash。(但是提包会失败。)
      • 但有些情况下,是 web 里的某些行为触发的一些逻辑,
        • 比如 iOS12 下 web 里可以提供上传视频录制的功能,此时会调用麦克风权限,如果此时 APP 没有配置麦克风相关的 隐私权限 描述,就会 crash。
        • 但有时候企业级证书并不 crash…
  • Provisioning Profile

    • 官方说明:A provisioning profile is a collection of digital entities that uniquely ties developers and devices to an authorized iPhone Development Team and enables a device to be used for testing.
    • 设备开发者授权的作用,他将 开发者账号证书entitlements文件以及设备进行了绑定。
    • Xcode 8及后续版本默认情况下会自动帮我们管理Provisioining Profile
      • 自动下载的Provisioning Profile都被存放在~/Library/MobileDevice/Provisioning\ Profiles/路径下
    • Apple iPhone OS Provisioning Profile Signing进行签名
    • 几项关键内容
      • DeveloperCertificates
        • 允许使用的开发者证书,这是一个列表,一般包含生成这个Provisioning Profile文件时,当前开发者账号下所有有效的Development证书
      • Entitlements
        • 允许使用的权限列表,实际在App中使用的权限必须是这个列表的子集,否则安装时会无法通过校验而失败。
      • ProvisionedDevices
        • 允许安装的设备列表,如果目标设备的UUID不在这个列表中,会安装失败
          • 普通开发者证书: 只允许最多注册100台用于测试的设备,避免任意分发测试包。
          • 企业级开发者证: 有任意安装的需求,因此在分发时,这一项会被ProvisionsAllDevices取代,代表授权任意设备。
    • 这些信息中有任何变动的时候,
      • 比如开发者证书有新增或者失效,在Capabilities中启用了当前App从未使用过的新功能,
      • 或是将新的iPhone连接到Xcode用于测试,
      • Xcode都会自动重新申请 Provisioning Profile。(前提是Xcode登录了对应的账号,否则还是手工操作吧)
    • Provisioning Profile最终会被内置在App中,置于App根目录下的embedded.mobileprovision
    • 但从 AppStore上下载下来的App,里面不会有embedded.mobileprovision这个文件,因为经过Apple重新签名以后,设备就不再需要它了。
    • 非AppStore安装App时如果签名校验通过,这个文件会自动被拷贝到iOS设备的/Library/MobileDevice/Provisioning\ Profiles/路径下。
    • 由于该文件已被Apple官方签名,系统可以无条件信任它,并用它来校验App的签名权限,以及本机的UUID等是否满足来自官方的授权。
    • 通过这种方式,间接信任了使用开发者证书签名的App,让iOS设备可以运行非苹果官方签名的App。

代码签名

  • 2008年苹果发布iOS2.0时引入了强制代码签名(Mandatory Code Signing)

    • 严格控制设备上能够运行的代码
    • 为iOS设备的安全性和苹果的AppStore生态奠定了坚实的基础。
    • iOS的代码签名是典型的数字签名
  • 好处:

    • 安全性
      • 保证设备及系统的安全性
        • 只有被苹果设备认可的证书签名的代码才能够被执行,否则在安装或者运行时会因为无法通过内核的签名校验而失败。
        • iOS的系统中内置了来自苹果的CA证书,系统自身的代码都是被苹果签名过的, 而用户从AppStore下载的App也都已被苹果官方进行签名。
        • 签名机制可以有效地防止来自外部的攻击。
      • 保障沙盒的有效运转,有效地限制app的行为
        • 沙盒的配置是绑定在签名中的,即Entitlements文件,也是强制签名保护的对象。
    • 分发控制
      • 苹果拥有App分发的绝对控制权。
      • 在iOS平台上(面向未越狱的用户)公开发行App的合法途径有且只有一种,就是上传到苹果官方的AppStore供用户下载。
        • 苹果会进行严格的审查签名
        • 企业包需要申请企业证书进行签名
  • 在编译iOS App时,Xcode在编译的打包的流程中会自动进行代码签名

    • 可以在编译日志界面找到一个Sign的步骤,内部是调用了codesign命令
  • Xcode 签名后的差异(对比无签名状态)

    • 多了一个_CodeSignature文件夹,只有一个文件CodeResources
      • plist文件,保存签名时每个文件除了App的可执行文件)的明文哈希值(单纯哈希,未加密)
        • 这里的哈希值哈希之后再base64编码的明文,并没有用私钥加密
      • filesfiles2 两个部分,分别是旧版本和新版本的文件列表,
        • rulesrules2两个部分描述了新旧版本计算hash时需要被排除的文件以及每个文件的权重
        • files中保存的是每个文件的sha1值,files2中同时保存了sha1sha256
    • 多了一个embedded.mobileprovision文件
      • 即对应的Provisioning Profile文件,直接拷贝到了app的根目录并重命名
    • 二进制文件内容有差异,签名后体积变大
      • 签名后二进制文件多了一个名为LC_CODE_SIGNATURELoad Command,即代码签名
      • 代码签名是一段纯二进制的数据,(具体结构定义详见苹果官方文档),其中包含如下内容
        • Code Directory (旧版本, sha1)
        • Requirements
        • Entitlements
        • Code Directory (新版本, sha256)
        • CMS Signature
      • Code Directory里面是整个MachO文件的哈希值
        • 不是一次性对整个文件进行哈希
        • 而是将MachO文件按照pageSize(一般是4k即4096字节)进行分页,每一页单独计算哈希(单纯哈希,未加密),并按顺序保存。
        • 有两个Code Directory,除哈希类型,其他内容基本一样
        • 文件不一定是pageSize的整数倍,最后一页往往不足,用额外的字段codeLimit记录文件的实际大小
          • 通过这个值算出最后一页的实际大小,并提取相应数据计算最后一页的签名。
        • MachO外,在第一页的前面,还有5个特殊的负数页,用来保存以下这些额外信息的哈希值。(也是单纯哈希,未加密)
          • -1 App根目录的Info.plist文件
          • -2 Requirements(代码签名的第二部分)
          • -3 Resource Directory (_CodeSignature/CodeResources文件)
          • -4 暂未使用
          • -5 Entitlements (代码签名的第三部分)
      • Requirements
        • 用于指定签名校验时的一些额外的约束,签名时codesign命令会自动生成这部分数据,细节忽略,官方文档有一些描述
      • Entitlements
        • 整个被嵌入到签名数据中
        • 但不是工程中原始的,Xcode混合上一些默认的参数信息生成的。
      • CMS Signature
        • 最重要的部分
        • Cryptographic Message Syntax,一种标准的签名格式,和Provisioning Profile的签名格式相同
        • 内容包括但不限于:
          • 证书链:包含用于签名的开发者证书及所有上游CA的证书
          • 签名者信息
          • 哈希算法
          • 签名算法(对哈希值进行加密所使用的算法)
          • 加密后的哈希值
          • signedAttrs字段
            • 需要签名的属性, 是可选项
            • 为空表示被签名的数据是原始文件的内容,
            • 如果不为空则至少要包含原始文件的类型以及其哈希值,此时被签名的数据就是signedAttrs的内容
            • 先计算被签名数据的哈希,然后再对哈希值进行签名
        • Code Directory哈希值CDHash作为数据记录在signedAttrs中,作为最终真正被签名(计算哈希+加密)的内容。
          • signedAttrs 中再对 CDHash 进行 签名(计算哈希+加密)
    • 总结一波加密数据情况
      • _CodeSignature/CodeResources中对每个资源文件计算哈希
      • 二进制中 Code Directory 中对MachO文件本身的每个分页,以及Info.plistCodeResourcesEntitlements等文件计算哈希
      • 计算 Code Directory哈希CDhash,存储在二进制中 CMS SignaturesignedAttrs
      • 二进制中 CMS SignaturesignedAttrs再对signedAttrs进行 签名(计算哈希并使用开发者的私钥加密)
    • 只有最后一步的涉及真正加密, 前面几步的均为单纯,只要任意内容有变化,均会因某个环节的哈希不匹配而导致签名校验的失败。
  • 导出 or 提交App

    • 在Xcode Organizer中导出或者提交App时,Xcode会将Entitlements文件及embedded.mobileprovision文件替换为对应的版本
    • 并使用对应的证书重新签名
      • AppStore:不可调试,推送为生产环境,无ProvisionedDevices,发布证书
      • Ad Hoc:不可调试,推送为生产环境,允许安装到已注册的测试设备,发布证书
      • Development:可调试,推送为测试环境,允许安装到已注册的测试设备,开发证书
      • Enterprise:不可调试,推送为生产环境,ProvisionAllDevices,企业级发布证书
  • App 安装时

    • /usr/libexec/installd完成
    • installd会通过libmis.dylib校验ProvisioningProfileEntitlements及签名的合法性
      • 递归地校验签名时每一个步骤生成的哈希值:CDHash, Code Directory, _CodeSignature/CodeResources
  • App 启动时

    • loader会先将可执行文件加载到虚拟内存,在加载的过程中mach_loader会自动解析MachO文件中的LC_CODE_SIGNATURE并进行校验
    • load_code_signature在解析完签名的数据后会调用mac_vnode_check_singature函数进行验证
      • 最终也是调用了libmis.dylib来实现签名的校验,这一校验过程基本与安装时一致,防止安装后的篡改。
    • 加载过程中为了提升加载效率,签名校验并不会去检查Code Directory实际的代码是否匹配,仅检查了CMS SignatureCode Directory Hash的合法性。
  • APP 运行时

    • 一页代码被加载到虚拟内存后,会立即触发page fault,会判断代码页是否需要签名校验
    • 如果需要,则计算当前代码页的哈希值,并与签名中Code Directory记录的值进行比对。如果不符,且不满足系统预设的例外条件,则会向内核发出CS_KILL指令。
  • 有关越狱

    • 越狱之后,签名校验机制会被破坏掉。
    • 在iOS6/7时代,典型的方式是替换 libmis.dylib中的_MISValidateSignature函数,使其永远返回验证成功,简单粗暴。
    • 这样既可在沙盒范围内,声明任意的权限。
  • 重签名

    • 有时出于各种原因,我们需要对一个App进行重签名,方便在自己的设备上进行测试。
    • 需要使用和原App相同或者至少包含原App所需权限的Entitlements文件
    • Entitlements文件中还有一些跟Team IDApp ID相关的配置
      • 因为我们不能使用已经被其他开发者注册过的ID。使用自己的ID一般也不会有什么问题,但在某些情况下可能导致最终的程序逻辑出现异常,这根具体的代码实现细节有关。
      • 正常签名的App中,Entitlements文件中标识的application-identifierBundle ID)和Info.plist中的CFBundleIdentifier的值是相同的,
      • 但实际在签名校验过程中,系统不检查二者是否一致,所以即使不一致,也不影响重签名之后的运行。
    • 只要确保有正确的EntitlementsProvisioning ProfileEntitlements匹配,且包含重签时使用的证书及目标设备的UUID,就可以进行重签名了
    • 另需要注意,App中除了可执行程序文件外,还会可能会有FrameworksPlugins,里面都会包含二进制的代码文件,他们的哈希值也会被存储在 _CodeSignature/CodeResources 中。所有的二进制代码都必须进行签名,而签名后二进制文件的哈希值就会产生变化,因此需要先对这两个文件夹下的二进制文件进行签名,再对App进行签名。
    • (有各种现成的工具可以重签名)

有关 APNs

  • Apple Push Notification service

  • 苹果推送通知服务

  • 服务端两种认证方式

    • 基于证书的信任认证(旧版,更常用一些)
      • 服务端使用推送证书(推送证书的 .p12文件)与APNs通信
      • 每个App 需要单独配置。推送证书的有限期为1年(生产的推送证书比开发的推送证书多一个月),过期之后需要重新配置。
      • 现在的生产推送证书已经可以同时支持 sandbox 和 生产环境。
    • 基于Token的信任认证(新版,2016年)
      • 在开发者平台申请APNs Auth Key,得到包含 APNs Auth Key.p8密钥文件。
      • .p8 文件只能下载一次
      • 服务端使用包含token(APNs Auth Key)的.p8文件与APNs通信
      • 一个认证密钥可用于同一个账号下多个App服务,而且永远不过期,可以重新申请,使旧的失效。
  • 服务器与 APNs 通信的时候,必须实现上述两种认证方式之一

  • device token

    • 是一个不透明的NSData,包含一个设备上的一个应用的唯一标识。
    • 只有 APNs 能解密并查看 device token 中的内容。
    • APNs 提供的 device token长度不定,不要强行解码其大小
  • 大概的流程

    • APP 安装
    • APP 启动,APP 向 APNs 发送远程推送注册请求
    • 苹果给 APP 提供唯一的 device token
      • 回调方法 application:didRegisterForRemoteNotificationWithDeviceToken:
    • APP 将自己的 device token 转发给自己的 服务端
      • 一般按二进制或十六进制的格式发给你的服务器
    • 服务端 做好相应的记录
    • 服务端 要发送推送消息时,使用 推送证书 + device token + 推送内容 来发送消息
    • APNs 进行校验,通过后,会向设备发送请求的推送信息
    • 设备 接收到 APNs 发来的推送之后,操作系统 会把通知递送给相应的 APP。
  • 一般情况下如果设备已经注册了远程推送请求,并且特定 APP 的 device token 并没有变化,则 APNs 会返回已经存在device token 到设备上

  • APNs 下发新的 device token 可能的原因:

    • 用户安装你的APP到新设备
    • 用户通过备份恢复设备
    • 用户重装系统后
    • 其它系统层面的事件
  • 所以,一般APP在启动的时候必须请求device token

简单实地操作

  • Apple ID

    • 注册
  • 开发者账号

    • 提交信息+交钱
    • 购买$99(¥688) / $299
    • 如果要公司或企业级,还需要提供邓白氏编码等信息,等苹果联系等等
  • 登录苹果开发者平台

  • 创建 APP ID

    • 根据引导,填写各种信息
  • 创建证书(开发)

    • Mac - 钥匙串访问 - 证书助理 - 从证书颁发机构请求证书
    • 填写开发者账号的邮箱,存储到磁盘即可 -> 生成 CSR(certSigningRequest)
      • 开发证书发布证书Push 证书,都需要各弄一个 CSR
      • 此时本地已经生成了对应的 公钥 + 私钥
    • 开发者平台 - Certifications,根据引导,选择对应的类型,上传 CSR,下载 Cer 文件
    • 双击安装,导入钥匙链, 自动与 CSR 对应的关联
  • 创建配置文件(描述文件Provisioning Profile)

    • 开发者平台 - Provisioning Profile,根据引导,
      • 选择对应的类型
      • 选择 APP ID
      • 选择证书
      • 选择设备
      • 取个名字
      • Generate
      • Download
    • 双击就添加到 Xcode 中
    • 也可以在Xcode中下载(当然,前提是登陆了对应的账号)
      • Xcode - Preference - Accounts
      • 即可管理相应的证书
  • 创建 APP

    • 选择对应的 APP ID
    • 填写 APP 信息
      • 记得现在需要隐私说明了
  • Xcode -> 创建项目

  • Project -> Build Setting

    • Signing
      • Code Signing Identity
        • 证书配置
          • Debug
          • Release
          • ReleaseInHouse(企业级证书)
        • Provisioning Profile
          • 对应配置
          • 也可以配置成 automatic,然后在 TARGETS 里配置
  • TARGETS -> General

    • Identity
      • Bundle Identifier: 即 Bundle Id
    • Siging
    • Sining(Debug): 选择Development开发证书对应的 Provisioning Profile
    • Sining(Relase): 选择Distribution发布证书对应的 Provisioning Profile
  • debug 打包

    • Xcode 点击 Run
  • 导出 ad hoc 或上传 App Store

    • Product - Archive
      • 需要有Distribution发布证书以及对应的Provisioning Profile,才能执行Archive
    • 生成的文件可以在 Window - Organizer 中的 Archives tab 中找到(Archive执行完毕后会自动打开)
    • 可以进行导出 ad hoc 包,或者上传 AppStore 等操作
      • 也可以在 Xcode - Open Developer Tool - Application Loader 来进行相关上传操作
  • 相关打包、上传等操作,可以通过 xcode 提供的命令行工具进行操作,可以手工指定各种参数、证书所在位置、证书ID等,方便的进行持续集成、自动化打包等工作。

参考