Code Signing探究

什么是Code Signing

Code Signing是用于验证一个APP是否由你创建的安全技术。一旦一个应用被签名,那么系统可以防御任何应用的变化,不管这个变化是认为还是无意或恶意的代码。


what_code_signing.png

在安装到设备上或者发布到App Store之前应用必须用Apple认证的证书进行签名。

概念描述

App ID

App ID用于标识一个或者一组App,App ID应该是和Xcode中的Bundle ID是一致的或者匹配的。App ID主要有以下两种:

  • 唯一App ID(Explicit App ID),这种App ID用于唯一标识一个应用程序,例如com.ABC.demo1,标识Bundle ID为com.ABC.demo1的程序。
  • 通配符App ID(Wildcard App ID),用于标识一组应用程序。例如可以表示所有应用程序,而com.ABC.可以表示以com.ABC开头的所有应用程序。

每创建一个App ID,我们都可以设置该App ID所使用的APP Services,也就是其所使用的额外服务。每种额外服务都有着不同的要求,例如,如果要使用Apple Push Notification Services,则必须是一个explicit App ID,以便能唯一标识一个应用程序。下面是目前所有可选的服务和相应的配置要求。

appid_services.png

Device

Device最简单了,就是iOS设备。Devices中包含了该账户中所有可用于开发和测试的设备。 每台设备使用UDID来唯一标识。

每个账户中的设备数量限制是100个。Disable 一台设备也不会增加名额,只能在membership year 开始的时候才能通过删除设备来增加名额。

证书(Certificate)

证书是用来给应用程序签名的,只有经过签名的应用程序才能保证他的来源是可信任的,并且代码是完整的, 未经修改的。在Xcode Build Setting的Code Signing Identity中,你可以设置用于为代码签名的证书。在申请Certificate之前,需要先申请一个Certificate Signing Request (CSR) 文件,而这个过程中实际上是生成了一对公钥(Public Key)和私钥(Private Key),保存在钥匙串访工具Keychain中,你可以看到所有你持有的私钥相对应的证书。要用一个证书设置代码签名,你必须拥有私钥,所以所有你拥有私钥的证书都会被列在这里。如果你拥有一个证书的私钥,你可以展开证书并将它的私钥显示出来。概括的讲,一个证书是一个公钥加上许多附加信息,这些附加信息都是被某个认证机构(Certificate Authority 简称 CA)进行签名认证过的,认证这个证书中的信息是准确无误的。

代码签名正是使用这种基于非对称秘钥的加密方式,用私钥进行签名,用公钥进行验证。如下图所示,在你keychain的login中存储着相关的公钥和私钥,而证书中包含了公钥。

pro1.png

login_key.png

你只能用私钥来进行签名,所以如果没有了私钥,就意味着你不能进行签名了,所以就无法使用这个证书了,此时你只能revoke之前的证书再申请一个。因此在申请完证书时,最好导出并保存好你的私钥。当你想与其他人或其他设备共享证书时,把私钥传给它就可以了。私钥保存在Mac中,而苹果生成的Certificate中包含了公钥。当用自己的私钥对代码签名后,苹果就可以用证书中的公钥来进行验证,确保是你对代码进行了签名,而不是别人冒充你,同时也确保代码的完整性等。对于 iOS 开发来说这个认证机构就是苹果的认证部门 Apple Worldwide Developer Relations CA。认证的签名有固定的有效期,这就意味着当前系统时间需要被正确设置,因为证书是基于当前时间进行核对。这也是为什么将系统时间设定到过去会对 iOS 造成多方面破坏的原因之一。

证书包含以下两大类型:

1.Development

  • App Development (1年):用来开发和真机调试应用程序。
  • Push Development (1年):用来调试Apple Push Notification

2.Production

  • In-House and Ad Hoc (3年):用来发布In-House和AdHoc的应用程序。
  • App Store :用来发布提交App Store的应用程序。
  • MDM CSR
  • Push Production (1年):用来在发布版本中使用Apple Push Notification。
  • Pass Type ID Certificate:用于通行证类证书
  • Website Push ID Certificate

如果你要导出证书,例如为了备份,一定要记得展开证书那一条显示出私钥并将两行都选中。

还有一种可以用来快速地显示出你的系统中能用来对代码进行签名的认证的方法,那就是利用用途广泛的命令行工具 security:

$ security find-identity -v -p codesigning                       
1) 01C8E9712E9632E6D84EC533827B4478938A3B15 "iPhone Developer: Thomas Kollbach (7TPNXN7G6K)"

Provisioning Profile

一个Provisioning Profile文件包含了上述的所有内容:证书、App ID、设备。

试想一下,如果我们要打包或者在真机上运行一个应用程序,我们首先需要证书来进行签名,用来标识这个应用程序是合法的、安全的、完整的等等;然后需要指明它的App ID,并且验证Bundle ID是否与其一致;再次,如果是真机调试,需要确认这台设备能否用来运行程序。而Provisioning Profile就把这些信息全部打包在一起,方便我们在调试和发布程序打包时使用,这样我们只要在不同的情况下选择不同的profile文件就可以了。而且这个Provisioning Profile文件会在打包时嵌入.ipa的包里。

例如,如下图所示,一个用于Development的Provisioning Profile中包含了该Provisioning Profile对应的App ID,可使用的证书和设备。这意味着使用这个Provisioning Profile打包程序必须拥有相应的证书,并且是将App ID对应的程序运行到Devices中包含的设备上去。

一个配置文件是一组信息的集合,这组信息决定了某一个应用是否能够在某一个特定的设备上运行。配置文件可以用于让应用在你的开发设备上可以被运行和调试,也可以用于内部测试 (ad-hoc) 或者企业级应用的发布。Xcode 会将你在 project setting 中选择的配置文件打包进应用。前面提到了,选择配置文件是 Xcode 6 才提供的功能,在 Xcode 5 或更早版本中,配置文件是 Xcode 根据你选择的签名证书来选择的。事实上同一个证书可以拥有多个不同的配置文件,因此让 Xcode 自行选择可能存在一些不确定性,最好的方式是你自主去选择,在 Xcode 6 中终于提供了这个功能。

pro1.png

如上所述,在一台设备上运行应用程序的过程如下:

pro2.png

Profile文件的种类是和所能创建的证书种类相关的,Provisioning Profile也分为Development和Distribution两种:

  • Development (1年)
  • Distribution (1年)
    • In House
    • Ad Hoc
    • App Store

iOS Team Provisioning Profile是第一次使用Xcode添加设备时,Xcode自动生成的,它包含了Xcode生成的一个Wildcard App ID(*,匹配所有应用程序),账户里面所有的Devices和所有Development Certificates,如下图所示。因此,team中的所有成员都可以使用这个iOS Team Provisioning Profile在team中的所有设备上调试所有的应用程序。并且当有新设备添加进来时,Xcode会更新这个文件。

temp_pro.png

我们下面来仔细研究一下配置文件。如果你要在自己的机器上找到配置文件,在这个目录下 ~/Library/MobileDevice/Provisioning Profiles。Xcode 将从开发者中心下载的全部配置文件都放在了这里。

不要惊讶,配置文件并不是一个 plist 文件,它是一个根据密码讯息语法 (Cryptographic Message Syntax) 加密的文件(下文中会简称 CMS,但不要用这个简写 Google,这不是一个很好的关键字)。如果你处理过 S/MIME 邮件或者证书你会对这种加密比较熟悉,详细信息可以查看互联网工程任务组 (IETF) 制定的 RFC 3852。

采用 CMS 格式进行加密使得配置文件可以被设置签名,所以在苹果给你这个文件之后文件就不能被改变了。配置文件的签名和应用的签名不是一回事,它是由苹果直接在开发者中心 (developer portal) 中设置好了的。

某些版本的 OpenSSL 可以读取这种格式,但是 OS X 自带那个版本并不行。幸运的是命令行工具 security 也可以解码这个 CMS 格式,那么我们就用 security 来看看一个 .mobileprovision 文件内部是什么样子:

$ security cms -D -i example.mobileprovision

这个命令会输出签名信息中的内容,如果你亲自试一下,接下来你会得到一个 XML 格式的 plist 文件内容输出。

这个列表中的内容是 iOS 用于判断你的应用是否能运行在某个设备上真正需要的配置信息,每一个配置文件都有它自己的 UUID 。Xcode 会用这个 UUID 来作为标识,记录你在 build settings 中选择了哪一个配置文件。

首先来看 DeveloperCertificates 这项,这一项是一个列表,包含了可以为使用这个配置文件的应用签名的所有证书。如果你用了一个不在这个列表中的证书进行签名,无论这个证书是否有效,这个应用都无法运行。所有的证书都是基于 Base64 编码符合 PEM (Privacy Enhanced Mail, RFC 1848) 格式的。要查看一个证书的详细内容,将编码过的文件内容复制粘贴到一个文件中去,像下面这样:

-----BEGIN CERTIFICATE-----
MIIFnjCCBIagAwIBAgIIE/IgVItTuH4wDQYJKoZIhvcNAQEFBQAwgZYxCzA…  
-----END CERTIFICATE-----`

然后让 OpenSSL 来处理 openssl x509 -text -in file.pem。

回到配置文件中继续往下看,你可能会注意到在 Entitlements 一项中包含了你的应用的所有授权信息,键值就和之前在授权那节看到的一模一样。

这些授权信息是你在开发者中心下载配置文件时在 App ID 中设置的,理想的情况下,这个文件应该和 Xcode 为应用设置签名时使用的那一个同步,但这种同步并不能得到保证。这个文件的不一致是比较难发现的问题之一。

举例来说,如果你在 Xcode 中添加了 iCloud 键值对存储授权 (com.apple.developer.ubiquity-kvstore-identifier),但是没有更新,重新设置并下载新的配置文件,旧的配置文件规定你的应用并没有这一项授权。那么如果你的应用使用了这个功能,iOS 就会拒绝你的应用运行。这也是当你在开发者中心编辑了应用的授权,对应的配置文件会被标记为无效的原因。

如果你打开的是一个用于开发测试的证书,你会看到一项 ProvisionedDevices,在这一项里包含了所有可以用于测试的设备列表。因为配置文件需要被苹果签名,所以每次你添加了新的设备进去就要重新下载新的配置文件。

Code Signing app的组成

一个已签名的可执行文件的签名包含在 Mach-O 二进制文件格式中;对于例如脚本这样的非 Mach-O 可执行文件,就存放在该文件系统的的扩展属性中。这种做法使得在 OS X 和 iOS 上的任何可执行二进制文件都可以被设置签名:不论是动态库,命令行工具,还是 .app 后缀的程序包。这也意味着设置签名的过程实际上会改动可执行文件的文件内容,将签名数据写入二进制文件中。

如果你拥有一个证书和它的私钥,那么用 codesign 来设置签名非常简单,我们现在尝试用下面列出的这个证书来为 Example.app 设置签名:

$ codesign -s 'iPhone Developer: Thomas Kollbach (7TPNXN7G6K)' Example.app

如果你想为某一个 app 程序包重新设置签名,那么这个工具就很有用了。为了重新设置签名,你必须带上 -f 参数,有了这个参数,codesign 会用你选择的签名替换掉已经存在的那一个:

$ codesign -f -s 'iPhone Developer: Thomas Kollbach (7TPNXN7G6K)' Example.app

codesign 还可以为你提供有关一个可执行文件签名状态的信息,这些信息在出现不明错误时会提供巨大的帮助。举例来说,$ codesign -vv -d Example.app 会列出一些有关 Example.app 的签名信息:

你需要查看的第一件事是以 Authority 开头的那三行。这三行告诉你到底是哪一个证书为这个 app 设置了签名。在这里当然是我的证书,iPhone Developer: Thomas Kollbach (7TPNXN7G6K)。我的这个证书则是被证书 Apple Worldwide Developer Relations Certification Authority 设置了签名的,依此类推这个证书则是被证书 Apple Root CA 设置了签名。

在 Format 中也包含了一些关于代码的信息:Example.app 并不单单是一个可执行文件,它是一个程序包,其中包含了一个 arm64 二进制文件。从 Executable 中的路径信息你可以看出,这是一个以测试为目的的打包,所以是一个 Mach-O thin 的二进制文件。

在一堆诊断信息中还包含了两个非常有趣的条目。 Identifier 是我在 Xcode 中设置的 bundle identifier。 TeamIdentifier 用于标识我的工作组(系统会用这个来判断应用是否是由同一个开发者发布)。此外用于发布应用的证书中也包含这种标识,这种标识在区分同一名称下的不同证书时非常有用。

现在这个二进制文件已经用证书设置好签名。就像中世纪人用蜡来封印信封一样,签名就这样封印了这个应用。下面我们来检查一下封印是否完好:
codesign --verify Example.app

就像大多数 UNIX 工具一样,没有任何输出代表签名是完好的。那么我下面破坏这个封印,只要修改一下这个二进制文件:

$ echo 'lol' >> Example.app/Example
$ codesign --verify Example.app
Example.app: main executable failed strict validation 

修改已经签名的应用会破坏封印,从命令行输出我们可以看到代码签名正如我们所预期一样起到了作用。

程序包和其他资源文件

对于命令行工具和脚本来说,只是一个可执行文件被设置签名,但是 iOS 和 OS X 的应用和框架则是包含了它们所需要的资源在其中的。这些资源包括图片和不同的语言文件,资源中也包括很重要的应用组成部分例如 XIB/NIB 文件,存档文件(archives),甚至是证书文件。所以为一个程序包设置签名时,这个包中的所有资源文件也都会被设置签名。

为了达到为所有文件设置签名的目的,签名的过程中会在程序包中新建一个叫做 _CodeSignatue/CodeResources 的文件,这个文件中存储了被签名的程序包中所有文件的签名。你可以自己去查看这个签名列表文件,它仅仅是一个 plist 格式文件。

这个列表文件中不光包含了文件和它们的签名的列表,还包含了一系列规则,这些规则决定了哪些资源文件应当被设置签名。伴随 OS X 10.10 DP 5 和 10.9.5 版本的发布,苹果改变了代码签名的格式,也改变了有关资源的规则。如果你使用10.9.5或者更高版本的 codesign 工具,在 CodeResources 文件中会有4个不同区域,其中的 rules 和 files 是为老版本准备的,而 files2 和 rules2是为新的第二版的代码签名准备的。最主要的区别是在新版本中你无法再将某些资源文件排除在代码签名之外,在过去你是可以的,只要在被设置签名的程序包中添加一个名为 ResourceRules.plist 的文件,这个文件会规定哪些资源文件在检查代码签名是否完好时应该被忽略。但是在新版本的代码签名中,这种做法不再有效。所有的代码文件和资源文件都必须设置签名,不再可以有例外。在新版本的代码签名规定中,一个程序包中的可执行程序包,例如扩展 (extension),是一个独立的需要设置签名的个体,在检查签名是否完整时应当被单独对待。

授权机制

到目前为止,我们都假设所有的证书起到的作用都是一样的,并且假设如果我们有了一个有效的证书代码签名也就相应的有效。然而这当然不是唯一的规则。操作系统有许多标准来检测你的代码是否允许运行。

这些标准并不是一成不变的。举例来说,在 OS X 上一个应用是否允许被开启是由 Gatekeeper 的选项决定的,你可以在系统设置的安全选项中改变选项。在 Gatekeeper 选项中选择 “受信任的开发者或者来自 Mac App Store” 会要求被打开的应用必须被证书签名,可以是 Mac App Store 开发者的应用发布证书也可以是开发者 ID 证书。这些选项是由一个系统工具 spctl 来管理的,它管理着系统的所有安全评估策略。

在 iOS 上规则是不一样的,无论是用户还是开发者都不能改变应用开启策略,你必须有一个开发者帐号或者应用发布证书才能让应用运行在 iOS 系统上。

即使你可以让应用运行起来,在 iOS 上你的应用能做什么依然是受限制的。这些限制是沙盒管理的。沙盒和代码签名机制是不同的,这很重要。代码签名保证了这个应用里所包含的内容正如它所说的那样不多不少,而沙盒则是限制了应用访问系统的资源。这两种技术是相互合作来发挥作用的,它们都能阻止你的应用运行,也都能在 Xcode 中引起奇怪的问题。但是在日常开发过程中,沙盒可能会更经常引起问题。沙盒机制在什么时候会引起问题呢,大多数情况下都是由于一个叫做授权的机制决定的。

授权机制决定了哪些系统资源在什么情况下允许被一个应用使用。简单的说它就是一个沙盒的配置列表,上面列出了哪些行为被允许,哪些会被拒绝。

很可能你已经猜到授权机制也是按照 plist 文件格式来列出的。Xcode 会将这个文件作为 --entitlements 参数的内容传给 codesign ,这个文件内部格式如下:

<?xml version="1.0" encoding="UTF-8"?>  
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">  
<plist version="1.0">  
<dict>  
    <key>application-identifier</key>
    <string>7TPNXN7G6K.ch.kollba.example</string>
    <key>aps-environment</key>
    <string>development</string>
    <key>com.apple.developer.team-identifier</key>
    <string>7TPNXN7G6K</string>
    <key>com.apple.developer.ubiquity-container-identifiers</key>
    <array>
            <string>7TPNXN7G6K.ch.kollba.example</string>
    </array>
    <key>com.apple.developer.ubiquity-kvstore-identifier</key>
    <string>7TPNXN7G6K.ch.kollba.example</string>
    <key>com.apple.security.application-groups</key>
    <array>
            <string>group.ch.kollba.example</string>
    </array>
    <key>get-task-allow</key>
    <true/>
</dict>  
</plist>  

在 Xcode 的 Capabilities 选项卡下选择一些选项之后,Xcode 就会生成这样一段 XML。 Xcode 会自动生成一个 .entitlements 文件,然后在需要的时候往里面添加条目。当构建整个应用时,这个文件也会提交给 codesign 作为应用所需要拥有哪些授权的参考。这些授权信息必须都在开发者中心的 App ID 中启用,并且包含在配置文件中,稍后我们会详细讨论这一点。在构建应用时需要使用的授权文件可以在 Xcode build setting 中的 code signing entitlements 中设置。

在这个应用中我启用了 iCloud 键值对存储 (key-value storage) (com.apple.developer.ubiquity-kvstore-identifier) ,以及 iCloud 文档存储 (com.apple.developer.ubiquity-container-identifiers)。另外我还将应用添加进了一个 App Group (比如说为了与扩展 (extensions) 共享数据,com.apple.security.application-groups), 最后开启了推送功能 (aps-environment)。这是一个开发版本,我会有将它连接到调试器的需求,这就需要将 get-task-allow 设为 true。另外,app id,也就是 bundle id 加上开发者 id,也被单独列出来了。

当然你并不能随心所欲的取得授权,你的应用能否得到某一项授权是有特定的规定的。举例来说,当 get-task-allow 被设定为 ture 时,应用只能在用于开发的证书签名下运行。你被允许使用的推送环境 (aps-environment) 也存在类似的限制。

根据操作系统版本的不同我们可选的授权项目是不一样的,所以很难有一份列表可以详尽地列出所有条目。至少在文档 Adding Capabilities 中提到的所有功能都是需要经过授权的。

授权信息会被包含在应用的签名信息中。如果你在这方面遇到了问题,可以尝试查看签名信息中具体包含了什么授权信息:$ codesign -d --entitlements - Example.app 会列出一个和前面的很像的 XML 格式的属性列表。你可以将这个文件的内容添加进一个脚本,每次构建应用时用脚本检查是否包含了推送服务的授权信息,以此确保推送服务工作正常。在这里推送服务只是一个例子,你使用的服务越多,这样的时候都添加推送通知的授权,以保证可以注册推送通知。在新版本的 Xcode 6 之后,授权信息列表会以 Example.app.xcent 这样的名字的文件形式包含在应用包中。在我看来,这么做是为了在出现配置错误时提供更加有用的错误信息。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 161,326评论 4 369
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,228评论 1 304
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 110,979评论 0 252
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,489评论 0 217
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,894评论 3 294
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,900评论 1 224
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,075评论 2 317
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,803评论 0 205
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,565评论 1 249
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,778评论 2 253
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,255评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,582评论 3 261
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,254评论 3 241
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,151评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,952评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,035评论 2 285
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,839评论 2 277

推荐阅读更多精彩内容