前言

做了几年iOS app coder了,对于证书的生成、使用流程烂熟于心,然而对于这套机制的原理却一直不甚理解。近来由于工作需要仔细研究了一下,特将自己的学习经验记录于此,以供大家学习指正。

问题

通常的iOS应用的签名流程是这样的(这里只是大概写一下,具体流程网上有很多):

  1. 访问钥匙串,使用“证书助理”的“从证书颁发机构请求证书”生成一个CertificateSigningRequest.certSigningRequest文件。
  2. 打开https://developer.apple.com,使用刚才生成的CSR文件生成证书(certifcate)。
  3. 在网站的Identitifiers里创建一个应用的ID。
  4. 在Devices里添加你想要测试使用的设备的UDID。
  5. 在Provisioning Profiles里生成.mobileprovision文件,需要勾选ID、UDIDs以及certificates。
  6. 将.cer文件和.mobileprovision文件下载到mac本地,双击,此时即可以在Xcode里的Build Settings页面进行选择,进行签名打包了。
  7. 此时可以通过钥匙串将对应的p12文件导出,在其他mac电脑上双击,它们也可以用此证书打包了。

那么问题来了,整个流程都被封装起来了,我们往返于钥匙串、开发者网站、Xcode之间,并不知道当中具体发生了什么。因而也就有了下面这些啰嗦。

概念

苹果需要我们对自己的应用签名,这样当用户从App Store里面下载到我们的应用时,用户就可以确定我们的应用来源于我们,并且从来没有被别人更改过。数字签名最重要的两个概念一个是证书,另一个是公钥加密(非对称加密)。苹果的数字签名机制使用的是X.509标准,依赖的加密算法是RSA。

Certificate

  数字签名的整个流程大概是这样的:对于要加密的文件,首先生成其对应的hash(一份文件对应唯一的hash),然后使用私钥对其加密,并记录下来。用户收到文件后,使用公钥解密得到hash,然后跟自己计算的文件的hash作对比。如果一样,则说明文件没有被修改过,且文件来源于与此公钥配对的私钥的持有者。

  但是只靠这一操作仍然有逻辑上的漏洞,即没人能证明用户手中的公钥是合法的。即A说B是合法的,但没人能证明A是合法的;如果A不合法,那也就不能保证B也是合法的。也就是说公钥证明了签名的私钥是和它配对的,但是并不能确定私钥确实来自它所说的签名者。

  因此,X.509协议引入了证书(certificate)来解决这一问题。证书就像我们的身份证一样,可以唯一的标识我们的身份,并且包含着一些我们自身的相关信息。证书是由权威机构(CA,certification authority)颁布的(就像身份证由公安机关颁布,只要公私钥的持有者都信任它即可),并且具有有效期,如下图所示(在钥匙串中),CA是Apple Worldwide Developer Relations Certification Authority,过期日期是2016.11.16。

  另一方面,证书中包含着公钥,终端可以用它来验证信息。这样,整个链条就完整了:CA->证明证书(公钥)的合法性->公钥证明私钥加密信息的合法性,这样用户就可以确认数据来源于它所标识来自的人,并且没被修改过。

RSA

除了证书,另一个重要的概念就是RSA。RSA算法可用于加密(公钥加私钥解),也可用于数字签名(私钥加公钥解)。这里我们使用openssl来作为研究的工具。

首先我们生成一对尽量简单的密钥对,当然这很不安全:

openssl genrsa -out private.key 32
openssl rsa -in private.key -pubout > pubkey.pubkey

-----BEGIN RSA PRIVATE KEY-----
MCsCAQACBQCtbWYxAgMBAAECBDpl33UCAwDepwIDAMdnAgI/dwICcT8CAjMj
-----END RSA PRIVATE KEY-----

-----BEGIN PUBLIC KEY-----
MCAwDQYJKoZIhvcNAQEBBQADDwAwDAIFAK1tZjECAwEAAQ==
-----END PUBLIC KEY-----

这是一对长度为32位的密钥对,这个长度实际上是模数(modulus)的二进制长度,也就是说这个模数大于2的31次方小于2的32次方。我们可以看到内容是base64编码的,且私钥文件内容要比公钥长一些。通过命令可以看一下里面具体包含着什么:

openssl rsa -in private.key -text -noout
输出:
Private-Key: (32 bit)
modulus: 2909627953 (0xad6d6631)
publicExponent: 65537 (0x10001)
privateExponent: 979754869 (0x3a65df75)
prime1: 56999 (0xdea7)
prime2: 51047 (0xc767)
exponent1: 16247 (0x3f77)
exponent2: 28991 (0x713f)
coefficient: 13091 (0x3323)

openssl rsa -pubin -in pubkey.pubkey -text -noout
输出:
Modulus (32 bit): 2909627953 (0xad6d6631)
Exponent: 65537 (0x10001)

其中:

  1. modulus长度为32位,值为2909627953(10101101011011010110011000110001)
  2. modulus = prime1 * prime2
  3. publicExponent * privateExponent mod (prime1-1)*(prime2-1)=1
  4. (modulus, publicExponent) 公钥
  5. (modulus, privateExponent) 私钥

因而,如果其他人获取了我们的私钥文件,他就拥有了我们加密的一切。苹果开发者们的私钥一般保存在钥匙串中。RSA加密算法与解密算法一样(加密实际上与签名也是同一个算法,只是公私钥所担当的角色不同),假设A为明文,B为密文,则:

A=B^privateExponent mod modulus;B=A^publicExponent mod modulus

而公私钥可以互换使用,即:

A=B^publicExponent mod modulus;B=A^privateExponent mod modulus

关于如何使用openssl对文件进行签名、验证,这里不做过多描述,有兴趣可以看一下参考资料里的内容。

具体到iOS

那么,上面的概念在苹果的体系当中是如何工作的呢?大概是这样的:

  1. 首先,在钥匙串助手中生成CSR(.certSigningRequest)文件,此时你的电脑上已经生成了私钥和公钥。CSR文件中包含着公钥,以及一些其他的信息。
  2. 登录苹果开发者网站,如果你的账号有对应权限,你的信息就被确认合法了。然后将CSR上传到苹果开发者网站(CA),它会用它自己的私钥对文件信息进行加密,这会得到一个certificate文件,这包含着你的公钥以及其他的你的信息。
  3. 这样,开发者就得到了一对认证过的密钥对。然后在网站上生成provision profile,这个文件的作用是跟随app,使app只能在指定的iPhone上运行(例如Ad Hoc的只能在添加过UDID的手机上运行)。provision profile包含着应用的App ID以及谁能运行这个应用的信息。其中还包含着应用的certificate(公钥),这样用户的手机就能检测App的内容是否合法了。
  4. 当我们打包时,我们需要选择要使用的Code Signing Identity(在Build Settings中选择),这对应着钥匙串中的私钥。生成.app时,代码编译成二进制文件,而资源被拷贝到.app里。然后系统使用指定的私钥对每个文件的hash进行加密,资源文件的结果保存在.app下的_CodeSignature下的CodeResources里,而二进制文件写入到了文件里。一同被包含进来的还有包含着公钥的描述文件embedded.mobileprovision。
  5. 当用户安装应用后,iOS会用自己的公钥,也就是Apple Worldwide Developer Relations Certification Authority对embedded.mobileprovision中的证书进行验证,验证合法再使用证书中的公钥对app目录下的文件进行验证,如果也都合法则此app合法。

下面,将对每一步做一些详细的分析,看整个流程都发生了一些什么事。

Key

首先,我们要生成整个流程最核心的东西,即私钥、公钥;苹果使用的是2048位的密钥:

openssl genrsa -

这样我们就生成了私钥,而公钥可以从私钥文件中导出(由上文可知私钥文件中实际上包含着公钥的信息):

openssl rsa -in private.key -pubout > pubkey.pubkey

打开这两个文件,可以看到这两个文件分别被这样的字符串包围着:

-----BEGIN RSA PRIVATE KEY——
…
-----END RSA PRIVATE KEY——

-----BEGIN PUBLIC KEY——
…
-----END PUBLIC KEY——

中间的核心内容是base64的密钥(以及其他信息)。这样我们就有了一对未被CA认可的密钥对。

CSR

下一步我们需要生成给CA验证的CSR文件:

openssl req -new -key private.key -out ios-dev.csr

执行这个命令时会问你询问一些你的信息,这里填的信息并没有什么用,生成证书的时候苹果会用我们的开发者账户相关信息填进去。而真正重要的是它携带着的公钥,我们可以看一下:

openssl req -in ios-dev.csr -noout -pubkey

可以发现,公钥信息与之前生成的公钥是一样的。CSR包含着两部分信息,分别是subject信息,也就是之前问的你的那些信息,以及你创建的公钥。

Certificate

下一步,我们需要登录developer.apple.com来生成证书。具体步骤这里就不多说了,总之结果就是我们得到了证书(.cer)。我们要关心的是这一步发生了什么呢?

这一步CA需要验证你的信息是否值得信任,如果值得信任,则将你提供的CSR中的公钥用它的私钥进行签名,并取一些其他的信息,最终生成certificate,并返回给你。如何验证你的信息呢?之前你申请开发者账号时其实就已经验证过了。你的个人信息、银行卡账户信息等等,只要符合苹果的规定,它就认为你是合法的。所以,这一步苹果只是将你的公钥用它的私钥加密了而已。

我们可以将公钥从certificate中导出(假设苹果返回的是ios_distribution.cer):

openssl x509 -inform der -in ios_distribution.cer -pubkey -noout > cer_pub.pubkey 

对比可以发现cer_pub.pubkey和我们之前生成的pubkey.pubkey是相同的。

然后,我们可以看一下里面具体有什么:

openssl x509 -inform der -in ios_distribution.cer -text -noout

可以看到,这个证书的CA是Apple Worldwide Developer Relations Certification Authority,以及subject信息等等。

现在,我们需要将证书导入到钥匙串中:

openssl x509 -inform der -in ios_distribution.cer -out ios_distribution.pem
openssl pkcs12 -export -in ios_distribution.pem -inkey private.key -out test.p12 -name "Test Sign”

输入一个密码。此时,会在当前目录下生成一个test.p12文件。双击即可导入钥匙串(没密码将会导入失败)。或者通过下面的命令导入:

security import test.p12 -k ~/Library/Keychains/login.keychain

打开钥匙串,选择“登录”、“证书”,将会找到我们刚生成的证书:

可以看到,证书和私钥都有,因而这个证书是可以用来签名的。

那么在iOS端是如何验证这个证书是否合法,里面的公钥可用的呢?由于苹果引入了钥匙串,所以openssl应该是不好完成这件事的(而且根据苹果的说法,由于openssl版本之间的API一直在变动,所以它从来没有作为iOS的一部分提供过)。苹果在Mac系统里提供了一个命令行工具security,它可以用来处理钥匙串相关的操作。我们可以验证一下我们刚才生成的certificate:

security verify-cert -c ios_distribution.pem

输出:...certificate verification successful.

我们可以创建一个自签名的证书:

openssl req -x509 -nodes -days  -newkey rsa: -keyout selfsign.key -out selfsign.crt

然后验证一下:

security verify-cert -c selfsign.crt
输出:Cert Verify Result: CSSMERR_TP_NOT_TRUSTED

可以看到,这个证书是不被信任的。因而我们可以想见iOS中大概也是用类似的方式来确认embedded.mobileprovision中的证书的可信性的。

Sign

万事俱备,只欠东风。当我们编写完代码之后,我们就需要对代码进行签名了。苹果提供了一个工具codesign大大简化了工作的流程(当然也把具体的流程封装了起来)。只要知道了code sign identity(也就是证书的CN;证书需要保存着钥匙串中)就可以对app进行签名了:

codesign -s -f ‘{code sign identity}’ test.app

然后,我们可以看一下app的签名信息:

codesign -vv -d test.app

输出:

Executable=/Users/hy/Library/Developer/Xcode/DerivedData/test-hekagtjqikppkueajznajyogtpmc/Build/Products/Debug-iphoneos/test.app/test

Identifier=com.xxx.test

Format=app bundle with Mach-O universal (armv7 arm64)

CodeDirectory v=20200 size=618 flags=0x0(none) hashes=14+3 location=embedded

Signature size=4690

Authority=iPhone Distribution: xxx

Authority=Apple Worldwide Developer Relations Certification Authority

Authority=Apple Root CA

Signed Time=2016年8月8日 下午4:19:19

Info.plist entries=27

TeamIdentifier=VWEN6QTM5A

Sealed Resources version=2 rules=12 files=7

Internal requirements count=1 size=168

可以看到,app是由iPhone Distribution: xxx签名的,它是由Apple Worldwide Developer Relations Certification Authority签名的,而这个证书又是由苹果的根证书Apple Root CA签名的,因此整个链条是完整的。

具体的加密算法、hash算法都是经过苹果封装的,因而我们也无法知道签名是怎么计算出来的。只能大略的知道资源文件的签名储存在_CodeSignature/CodeResources里;二进制文件的签名则直接写入了该文件里面。感兴趣的话可以自己用不同证书签下名对比一下。

Provision Profile

下一步,我们需要将应用安装到手机上,这时就轮到Provision Profile(.mobileprovision)出场了。每个iOS的应用都要携带一个对应的描述文件,它在app根目录下名为embeded.mobileprovision。iOS需要通过这个描述文件来确定应用是否有权限运行。在Mac系统中,它储存在~/Library/MobileDevices/Provisioning Profiles目录下。在打包应用时,Xcode只是简单的把对应的证书从这个目录下拷贝到app目录下。下面让我们看一下它具体包含哪些东西。

使用命令来查看它的内容:

security cms -D -i test.mobileprovision

此时会输出一个XML,我们能够看到这个文件里包含着这些我们感兴趣的内容:

  • DeveloperCertificates 包含着之前生成的证书
  • application-identifier 应用ID
  • ProvisionedDevices 此证书可以安装的所有设备的UDID,仅出现在develop描述文件里

其实从网站的生成流程中我们也能看出它包含着什么。因为我们首先要创建一个App ID;然后如果是测试证书的话还需要录入测试设备的UDID;然后我们需要选择它包含哪个证书。

下面我们来验证一下里面是否包含着之前创建的证书:

security cms -D -i test.mobileprovision | perl -nle 'print $& if m{(?<=<data>).*?(?=</data>)}' | base64 -D > mobile.cer

上面这句命令就是先解析描述文件,其结果作为下面一句正则的输入;正则获取<data></data>之间的内容,也就是DeveloperCertificates所包含的证书(假设这里只有一个);取出的内容使用base64解码存入mobile.cer里。

比较mobile.cer和之前生成的ios_distribution.cer,发现两个文件是一样的。因而也就可以知道描述文件里确实包含着用于验证签名的公钥。

总结

以上就是我对苹果的代码签名机制的一些理解。文中忽略了一些内容,有的是因为时间问题,有的是因为与本文主题无关,有的是因为自己的疏忽。对于文中的不足以及纰漏,请不吝指教,先行谢过。

参考资料

RSA算法

http://www.matrix67.com/blog/archives/5100

http://www.ruanyifeng.com/blog/2013/06/rsa_algorithm_part_one.html

http://doctrina.org/How-RSA-Works-With-Examples.html

https://www.cs.cornell.edu/courses/cs5430/2015sp/notes/rsa_sign_vs_dec.php

证书

https://technet.microsoft.com/en-us/library/bb123848(v=exchg.65).aspx

https://www.sslshopper.com/what-is-a-csr-certificate-signing-request.html

http://www.shellhacks.com/en/HowTo-Decode-CSR

OpenSSL

http://www.cnblogs.com/interdrp/p/4881116.html

http://ngs.ac.uk/ukca/certificates/advanced

iOS代码签名

https://www.objc.io/issues/17-security/inside-code-signing/

http://www.stencyl.com/help/view/ios-certificates-guide

iOS代码签名理解的更多相关文章

  1. IOS证书/私钥/代码签名/描述文件

    1.   相关资源 (1)   钥匙串程序(常用工具->钥匙串),用于创建证书请求.安装证书.导出私钥等 (2)   IOS开发中心:https://developer.apple.com/de ...

  2. SQL Server安全(6/11):执行上下文与代码签名(Execution Context and Code Signing)

    在保密你的服务器和数据,防备当前复杂的攻击,SQL Server有你需要的一切.但在你能有效使用这些安全功能前,你需要理解你面对的威胁和一些基本的安全概念.这篇文章提供了基础,因此你可以对SQL Se ...

  3. 第六篇 SQL Server安全执行上下文和代码签名

    本篇文章是SQL Server安全系列的第六篇,详细内容请参考原文. SQL Server决定主体是否有必要的执行代码权限的根本途径是其执行上下文规则.这一切都可能复杂一个主体有执行代码的权限,但是却 ...

  4. java之jvm学习笔记六-十二(实践写自己的安全管理器)(jar包的代码认证和签名) (实践对jar包的代码签名) (策略文件)(策略和保护域) (访问控制器) (访问控制器的栈校验机制) (jvm基本结构)

    java之jvm学习笔记六(实践写自己的安全管理器) 安全管理器SecurityManager里设计的内容实在是非常的庞大,它的核心方法就是checkPerssiom这个方法里又调用 AccessCo ...

  5. iOS App签名的原理

    前言 相信很多同学对于iOS的真机调试,App的打包发布等过程中的各种证书.Provisioning Profile. CertificateSigningRequest.p12的概念是模糊的,导致在 ...

  6. es6 代码片段理解

    代码片段理解: [INCREMENT]: (state, action) => { const { payload: { id } } = action //because payload co ...

  7. iOS代码加密常用加密方式

    iOS代码加密常用加密方式 iOS代码加密常用加密方式,常见的iOS代码加密常用加密方式算法包括MD5加密.AES加密.BASE64加密,三大算法iOS代码加密是如何进行加密的,且看下文 MD5 iO ...

  8. arm-elf-gcc汇编代码个人理解

    arm-elf-gcc汇编代码个人理解 有关arm-elf-gcc的安装使用问题请参照本人博客的另一篇文章http://www.cnblogs.com/wsine/p/4664503.html 由于各 ...

  9. iPhone OS 开发 - 了解并解决代码签名问题

    译者:Jestery 发表时间:2010-04-24浏览量:21082评论数:0挑错数:0 了解并解决代码签名问题 (为保持跟开发环境以及APPLE开发者社区网站结构对应,一些名词未作翻译) 绝大多数 ...

随机推荐

  1. PYTHON 链接 Oracle

    一.  cx_Oracle Python 连接Oracle 数据库,需要使用cx_Oracle 包. 该包的下载地址:http://cx-Oracle.sourceforge.net/ 下载的时候,注 ...

  2. Solaris 自动挂载

    修改文件:vim /etc/vfstab添加命令:/dev/dsk/c2t0d0p0:1             -               /media/PARTITION1    pcfs   ...

  3. 没有VisualStudio也要HelloWorld

    前言 在博客园看到Artech的通过3个Hello World应用来了解ASP.NET 5应用是如何运行的(1)这篇文章,于是想跟着教程学习一下.说来惭愧,这篇文章发布于2014年12月,我在2016 ...

  4. Path形状获取字符串型变量数据

    Path形状获取字符串型变量数据: var path = new Path(); path.Data = Geometry.Parse("M 100,200 C 100,25 400,350 ...

  5. 如何手动让HttpRequestBase.IsAuthenticated 和 HttpContext.User.Identity.IsAuthenticated 为true.

    今天为了重写权限验证这块需要重写AuthorizeAttribute 这个属性,看了官方文档:HttpContextBase.User.Identity.IsAuthenticated 这个必须是tr ...

  6. Redis 高可用性解决方案(Sentinel)

    Sentinel是Redis的高可用性解决方案: 由一个或多个Sentinel实例组成的Sentinel系统可以监视任意多个主服务器,以及所有从服务器,并在被监视的主服务器进入下线状态时,自动将下线主 ...

  7. python爬取网站数据

    开学前接了一个任务,内容是从网上爬取特定属性的数据.正好之前学了python,练练手. 编码问题 因为涉及到中文,所以必然地涉及到了编码的问题,这一次借这个机会算是彻底搞清楚了. 问题要从文字的编码讲 ...

  8. USACO Section 3.1: Score Inflation

    完全背包问题 /* ID: yingzho1 LANG: C++ TASK: inflate */ #include <iostream> #include <fstream> ...

  9. Cloudera Manager5安装总结遇到问题及解决办法

    安装过程中,由于网络终端,导致下面问题: 问题1:安装停止在获取安装锁/tmp/scm_prepare_node.tYlmPfrT using SSH_CLIENT to get the SCM ho ...

  10. delphi下,不错的多语言翻译组件

    http://yktoo.com/en/software/dklangTraned http://sourceforge.net/projects/dklang/