1. 代码签名
代码签名并不是每一个iOS开发人员的首要任务,但是对代码签名工作原理的深入了解对于解决问题以及在开发团队中树立自己的形象非常有用。没有什么比一个可以重新签署过时的Swift 2.2 iOS应用程序的开发人员更“值得一提”了,而不是在时间不允许的情况下修复潜在的数千个Swift编译器错误。
下面会讲解代码签名工作原理的基本概述。在下面的链接找到开源iOS Wordpress v10.9应用程序:
在发送到iTunes Connect Store
之前,我们会探索应用程序代码签名的各个阶段。此外,我们会重新签署Wordpress
应用程序,以便它可以在我们自己的iOS设备上运行!
1.1 准备工作
我们需要一个合适的iOS苹果开发人员帐户来生成配置文件;需要一个真机来安装Wordpress iOS应用程序;还需要获取并安装mobdevim
命令,可在此处获得:
这个小命令行工具可以做很多事情,包括在设备上安装应用程序、查询设备信息和连接到iOS设备时获取控制台日志。使用此工具可以更轻松地在设备上安装iOS应用程序,并在应用程序签名不正确时查找错误。
按照mobdevsim
上的说明安装该工具。安装mobdevim
后,连上iOS设备并进行测试运行。
mobdevim -f
1.2 专业用语
要真正了解代码签名的工作原理,我们需要了解三个关键组件:公钥/私钥、授权文件和配置文件。我们将从广度优先开始,然后深入到深度优先。
公钥/私钥用于签署我们的应用程序。这是我们的数字签名,苹果知道如何进行验证。私钥用于对应用程序及其授权文件进行密码签名。授权文件实际上只是嵌入在应用程序中的一个XML
字符串,它表示应用程序可以做什么,不能做什么。
授权文件、已批准设备的列表和用于验证代码签名的公钥都捆绑在应用程序的配置文件中。所有信息都是通过私钥创建的签名强制执行的,并且可以通过公钥进行验证。
1.3 公钥/私钥
在学习代码签名过程时,这可能是最难理解的事情。因为公钥/私钥引入了密码学,不断深入将是个无底洞。
简单地说,有两种不同类型的密码学:对称加密和非对称加密。
- 对称加密,是一种只包含一个密钥的密码。如果A试图向B发送一条秘密消息,他们都必须知道共享的秘密才能对该消息进行加密和解密。
- 在非对称加密中,有两个密钥:公钥和私钥。A和B都有自己独特的私钥和独特的公钥。这样,他们就可以在任何人不知道对方私钥的情况下共享信息。
我们在设置Apple开发人员帐户时,已经完成了从证书颁发机构请求证书的过程。我们创建了一对公钥/私钥,将公钥发送到Apple服务器(通过.csr文件)。最终的结果是创建了一个由苹果签署的签名,这就是苹果如何唯一地识别你。这意味着苹果公司和我们都使用非对称加密技术来分发应用程序。
我们可以使用以下终端命令查看用于为应用程序签名的公钥/私钥对的名称或标识:
security find-identity -p codesigning -v
此命令查询macOS系统的keychain
,查找包含私钥(-v
)且其类型可以进行代码签名(-p
代码签名)的有效标识。
此输出将显示有效的标识,这些标识可以生成代码签名的应用程序。如果我们查找包含短语“iPhone Developer”的标识,则该标识可能用于在设备上签名iOS应用程序。
请注意,我们有一个公钥或证书,以及下面的私钥。证书可以重新创建,但私钥的价值超过黄金。永远不要删除私钥!如果这样做了,你就要拿出你是你的证据,就需要通过苹果重新创造一个新的身份。
从这个意义上说,证书只是公钥。因此,如果要使用keychain
访问来导出标识,并且希望将其格式化为.cer
格式,那么我们将只会导出公钥。如果还要导出私钥,则必须使用PKCS12格式(.p12
)正确导出完整标识、私钥和其他信息。
这一点很重要,因为我们需要知道是否要导出标识,以便另一个开发人员可以生成具有匹配分发标识的构建。但要小心:无论谁拥有私钥,都可以为那家公司承担全部身份,至少从苹果的角度来看是这样!
可以使用以下命令导出公共证书:
security find-certificate -c ${name} -p
这将把名字叫${name}
的公共证书输出到stdout
,并将其格式化为PEM
格式。显示证书有两种方法:DER
和PEM
。PEM
可以被终端读取(因为它是base64
编码的;DER
,用高度专业的编码术语来说,会产生官话并使终端发出很多哔哔声。
重复上述命令并将输出写入/tmp/public_cert.cer
。
security find-certificate -c ${name} -p > /tmp/public_cert.cer
然后就可以在终端查看了。
cat /tmp/public_cert.cer
-----BEGIN CERTIFICATE-----
MIIFnDCCBISgAwIBAgIIFMKm2AG4HekwDQYJKoZIhvcNAQELBQAwgZYxCzAJBgNV
BAYTAlVTMRMwEQYDVQQKDApBcHBsZSBJbmMuMSwwKgYDVQQLDCNBcHBsZSBXb3Js
ZHdpZGUgRGV2ZWxvcGVyIFJlbGF0aW9uczFEMEIGA1UEAww7QXBwbGUgV29ybGR3
...
这就是我们怎么知道这个证书在PEM
中。我们可以使用openssl
命令查询公共的x509
证书:
openssl x509 -in /tmp/public_cert.cer -inform PEM -text -noout
-
x509
选项表示openssl
命令应该使用x509
证书。 - 提供公共证书路径的
-in
,解码格式为PEM
(-inform PEM
)。 - 希望证书采用可读的文本格式
-text
选项。 - 指定不希望输出证书中带有
-noout
参数。
有关此公共证书的信息将显示在终端中。
记住这个openssl
命令。当我们阅读到将这些公共证书嵌入其中的配置文件时,将重新接触到x509
证书的概念。
1.4 授权文件
嵌入在几乎每个编译的应用程序中的是一组授权文件。这是嵌入在应用程序中的XML
字符串,表示应用程序可以做什么和不能做什么。其他程序将检查授权文件中的权限,并相应地授予或拒绝请求。
其中许多权限检查是由其他检查程序权限的守护程序执行的。例如,App Groups
、iCloud Services
、Push Notifications
、Associated Domains
都将修改应用程序的授权信息。Xcode中显示的这些功能只是苹果平台上的一小部分权利,因为大多数功能都是苹果私有的,并通过代码签名来实现。
可能最重要的授权,是get-task-allow
授权,可以在使用开发人员证书编译的所有软件上找到。它允许相关程序附着到调试器上。
在macOS上,可以通过为任何不具有get-task-allow: true
的应用程序禁用SIP来解决缺少此权限的问题。在iOS上,我们将尝试调试不具有此权限的应用程序,除非通过越狱禁用了代码验证。
可以通过codesign
命令查看应用程序的授权信息,比如Finder
:
codesign -d --entitlements :- /System/Library/CoreServices/Finder.app/Contents/MacOS/Finder
-d
选项表示在命令后面立即显示--entitlements
选项。:-
有两种作用:
-
-
打印到stdout
-
:
表示省略有信息和长度。
就像在Mach-O
中一样,代码签名信息是用一个magic header
和长度存储的。:
表示从输出中删除此头信息,并且只显示实际的XML
授权字符串。
1.5 配置文件
设置配置文件把公共x509
证书、已批准设备的列表以及授权文件嵌入到一个文件中。
配置文件的默认位置可以在以下位置找到:
~/Library/MobileDevice/Provisioning Profiles/
// 通过ls命令查看文件夹下的所有证书
ls ~/Library/MobileDevice/Provisioning\ Profiles/
不幸的是,配置概要文件是由它们的UUID
命名的,而不是由我们(或Xcode)为它们取的名称命名的。
幸运的是,我们可以再次使用security
命令来显示原始信息。选择任何一个.mobileprovision
文件并执行security
命令,如下所示:
PP_FILE=$(ls ~/Library/MobileDevice/Provisioning\ Profiles/ *mobileprovision | head -1)
security cms -D -i "$PP_FILE"
第一个命令获取一个配置文件并将其分配给PP_FILE
变量。PP_FILE
变量被传递到security
命令中。该命令对设置配置文件的加密消息语法cms
格式进行-D
解码,并通过-i
选项指定输入路径。
下面将讨论某一个配置文件的输出。输出中有一些有意思的点:
- AppIDName是绑定到此设置配置文件的ID的名称,可在https://developer.apple.com/account/ios/identifier/bundle上找到。
- TeamIdentifier是Apple为团队标识提供的唯一团队标识。苹果将为付款的每个账户生成一个特定的团队ID。例如,对于应用商店版本,会有一个唯一的团队ID;而对于企业版本,会有一个不同的团队ID。
-
授权包含应用程序使用此签名可以做什么和不能做什么。这通常是Xcode生成的配置文件出现问题的原因。因为Xcode需要更新
App ID
配置(本质上是授权),然后生成具有正确值的新配置文件。 - IsXcodeManaged是一个布尔值。表明Xcode是否管理这个配置文件。整个代码签名过程给开发者带来了很多麻烦,苹果公司正试图在IDE上做更多的工作,包括用我们的发行证书为应用程序签名。这是一把双刃剑,让Xcode代为管理方便了我们,但是如果Xcode做了一些我们没有预料到的事情,那么潜在的错误可能更难追踪。
- Name是在https://developer.Apple.com/account/ios/profile/limited上显示的用于标识配置文件的名称。
- ProvisionedDevices授权设备的列表。
-
DeveloperCertificates是包含
base64
编码的x509
证书的数组。这将包含先前通过security find certificate
命令提取的相同公共证书。在对应用程序进行代码签名时,这些证书也被编码到实际的可执行文件中。
1.6 探索WordPress app
与iOS设备上的典型调试工作流一样,在将应用程序发送到Apple iTunes Connect
之前,必须编译具有配置文件的应用程序。每个上架前的App都有一个名为embedded.mobileprovision的配置文件。正是这个配置文件告诉iOS应用程序是有效的,并且来自我们。
打开Pre App Store
目录中的WordPress.app
。为WORDPRESS.app
的完整路径分配一个终端变量WORDPRESS
,如下所示:
WORDPRESS="/full/path/to/WordPress.app/"
配置文件
在WordPress
应用程序中找到embedded.mobileprovision配置文件,并对其使用security命令。
security cms -D -i "$WORDPRESS/embedded.mobileprovision"
在这个特定的配置文件中,可以看到以下内容:
- 苹果公司分配给Automattic, Inc.公司的团队标识是
3TMU3BH3NK
。 - Wordpress应用程序使用iCloud服务:在授权字典中有com.apple.developer.icloud键。它还希望使用某些扩展,如App Groups。
- get-task-allow为false,意味着我们无法附着调试器。因为应用程序是使用分发签名标识签名的。
从DeveloperCertificates密钥复制base64
编码的数据。通过终端,将此值分配给名为CERT_DATA的变量:
CERT_DATA=MIIFozCCBIu...
变量CERT_DATA现在包含用于签署应用程序的base64
编码x509
证书。现在,解码这个base64数据
并将其导出到/tmp/wordpress_cert.cer
。之后执行openssl命令进行查看。
echo "$CERT_DATA" | base64 -D > /tmp/wordpress_cert.cerecho "$CERT_DATA" | base64 -D > /tmp/wordpress_cert.cer
openssl x509 -in /tmp/wordpress_cert.cer -inform DER -text -noout
Certificate:
Data:
Version: 3 (0x2)
Serial Number: 786948871528664923 (0xaebcdd447dc4f5b)
Signature Algorithm: sha256WithRSAEncryption
Issuer: C=US, O=Apple Inc., OU=Apple Worldwide Developer Relations, CN=Apple Worldwide Developer Relations Certification Authority
Validity
Not Before: Jan 17 13:26:41 2018 GMT
Not After : Jan 17 13:26:41 2019 GMT
Subject: UID=PZYM8XX95Q, CN=iPhone Distribution: Automattic, Inc. (PZYM8XX95Q), OU=PZYM8XX95Q, O=Automattic, Inc., C=US
...
这意味着,在Automattic, Inc的工作人员的keychain
上有一个名为“iPhone Distribution: Automattic, Inc. (PZYM8XX95Q)”的标识,用于签署此应用程序。
嵌入的可执行文件
如果应用程序包含扩展(即share
扩展、today
小部件或其他的),则在./Plugins目录中会找到更多签名的打包包,其中包含它们自己的应用程序标识符和embedded.mobileprovision配置文件。
这些功能为应用程序提供了应用程序之外的附加功能。
在深入./Plugins
目录中的容器时,可以使用相同的安全命令来验证这一点,分别查看每个embedded.mobileprovision文件。
_CodeSignature文件夹
包含在真正的iOS应用程序包(不在模拟器中)中的是一个名为_CodeSignature的文件夹,其中包含一个名为CodeResources的文件。这是一个XML plist文件,它是该目录中找到的每个不可执行文件的校验和。例如:
cat "_CodeSignature/CodeResources" | head -10
<?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>files</key>
<dict>
<key>AboutViewController.nib</key>
<data>
rSZAWMReahogETtlwDpstztW6Ug=
</data>
可以看到AboutViewController.nib文件的校验和
rSZAWMReahogETtlwDpstztW6Ug=。
这个值可以通过openssl
自己计算:
openssl sha1 -binary "AboutViewController.nib" | base64
rSZAWMReahogETtlwDpstztW6Ug=
苹果已经开始用Xcode 10为iOS应用程序从SHA-1
校验和向SHA-256
过渡,为两种算法生成校验和。
这个CodeResources
文件本身有一个对文件执行的校验和,它被嵌入到了实际的WordPress
应用程序中!这意味着,如果用户要修改任何文件,甚至在.app
目录中添加一个目录而不重新签名WordPress
应用程序,iOS应用程序都无法在用户手机上进行安装。
1.7 对WordPress app重新签名
我们可以使用Apple签名重新签名应用程序,将WordPress应用程序安装到iOS设备上。
从高层的角度来看,您需要执行以下操作:
- 将有效的配置文件复制到应用程序
WordPress .app
目录中的embedded.mobileprovision。 - 将
Info.plist
键CFBundleIdentifier
更改为新的配置文件中新的应用程序标识符。 - 使用适当的授权信息(也包括在配置文件中)通过嵌入的配置文件中包含的标识为
WordPress
应用程序重新签名。
如果你有一个有效的、未过期的、包含你的iOSUDID
的配置文件。连接上iOS设备,通过执行mobdevim -f
命令来获取设备的UDID
。
复制配置文件
无论是创建一个新的配置文件或使用现有的配置文件,我们将使用它来重新签名WordPress
应用程序。用它覆盖WordPress.app
文件夹下面的embedded.mobileprovision
文件。
PP_PATH=~/Downloads/Code_Signing_Example_ProvisProfile_92618.mobileprovision
cp "$PP_PATH" "$WORDPRESS/embedded.mobileprovision"
删除插件
WordPress
应用程序在主应用程序中嵌入了几个./Plugins
目录中的扩展应用程序。每个配置文件都包含一个具有唯一应用程序标识符的唯一配置文件。我们可以为每个扩展本身提供一个唯一的配置文件进行签名。但这个太麻烦了,不是重点。我们不使用这些扩展,删除WordPress
应用程序的整个插件目录。
修改Info.plist
希望你记住了配置文件的应用程序ID的名称,现在需要把它设置到Info.plist
的键CFBundleIdentifier
中。如果不记得,可以从配置文件中查询它。以下是获取这些信息的方法:
security cms -D -i "$PP_PATH" | grep application-identifier -A1
<key>application-identifier</key>
<string>H4U46V6494.com.selander.code-signing</string>
下面替换这个CFBundleIdentifier
键中的值。
plutil -replace CFBundleIdentifier -string H4U46V6494.com.selander.code-signing "$WORDPRESS/Info.plist"
我们还可以更改它的显示名称:
plutil -replace CFBundleDisplayName -string "Woot" "$WORDPRESS/Info.plist"
提取授权信息
下一个任务是使用配置文件中的有效授权重新签名应用程序。
由于授权在配置文件中嵌入为字典而不是XML,因此可能更容易先从主可执行文件中提取权限,然后用配置文件中找到的新权限修补该文件。提取授权信息导出到/tmp/ent.xml
:
codesign -d --entitlements :/tmp/ent.xml "$WORDPRESS/WordPress"
可以通过cat
进行查看:
cat /tmp/ent.xml
如果授权有效,则可以从当前配置文件中提取授权并将其放入这个新文件中。
首先,将配置文件XML写入名为/tmp/scratch
的文件:
security cms -D -i "$PP_PATH" > /tmp/scratch
使用xpath
命令只将权利信息提取到剪贴板。
xpath /tmp/scratch '//*[text() = "Entitlements"]/following-sibling::dict' | pbcopy
现在剪贴板中有有效的授权信息。打开/tmp/ent.xml
,删除包含的<dict>
之间的内容并替换为剪贴板的内容。最终的/tmp/ent.xml
文件应该如下所示:
<?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>keychain-access-groups</key>
<array>
<string>H4U46V6494.*</string>
</array>
<key>get-task-allow</key>
<true />
<key>application-identifier</key>
<string>H4U46V6494.com.selander.code-signing</string>
<key>com.apple.developer.team-identifier</key>
<string>H4U46V6494</string>
</dict>
</plist>
重新签名WordPress
我们已经执行了所有设置。我们有一个有效的签名标识;在embedded.mobileprovision
的WordPress
应用程序中嵌入了一个有效的配置文件;删除了插件目录;并且拥有在/tmp/ent.xml
中找到的新配置文件的授权。
在WordPress
应用程序Frameworks目录中使用codesign
命令和签名标识:
codesign -f -s "iPhone Developer: Derek Selander (8AW8QLCX5U)" "$WORDPRESS"/Frameworks/*
然后对文件夹进行签名:
codesign --entitlements /tmp/ent.xml -f -s "iPhone Developer: Derek Selander (8AW8QLCX5U)" "$WORDPRESS"
看看能否安装WordPress
应用程序。在终端中,键入:
mobdevim -i "$WORDPRESS"
如果我们使用开发人员配置文件签署了应用程序。那么我们将拥有get-task-allow
权限,这意味着我们可以调试这个WordPress
应用程序。