Companies often need to re-sign custom apps which
they have commissioned for deployment in an enterprise app store.
Typically this is done with tools available on an Apple Mac
(specifically, a tool called “codesign”
that can either be invoked directly or via “Xcode”
– an integrated development environment). However, Apple Mac devices
are not commonly available in many companies and the question is
sometimes asked whether it is practical to
sign such apps using tools that can run under Microsoft Windows.
An Apple app is actually a ZIP archive with a naming
convention for certain core files. The top-level folder is always called
“Payload” and this contains a single folder named after the application
(e.g.
Example.app). The first core file is called
Info.plist and would be found at this path in the archive:
Payload/Example.app/Info.plist
The extension “.plist” indicates that this is a “property list” file and some of the properties in this file are essential to signing and executing the
app. For example, the property “CFBundleExecutable” identifies the file that is the main “executable” file for the app and “CFBundleIdentifier”
specifies the globally unique identifier for the app. As
the Wikipedia article mentions, there are a variety of representations
of property list files, one of which is an XML format; this XML format
is the most easy to manipulate under Windows. If re-signing the app will
require changes to
Info.plist (more on this later), then
Info.plist can be converted into the XML format (if it is not
already in this format), modified and saved in XML format (there is no
need to convert it back into its original format).
Signing a Mach-O binary app
If the main executable is a
Mach-O binary file then (normally) the bulk of the code signing
information is embedded in this file. A Mach-O file can be “fat”
(contains a collections of machine architecture specific slices) or
“thin” (contains just the code for one machine architecture).
Each machine architecture specific slice is signed individually, so
signing a “fat” Mach-O file is equivalent to individually signing a
collection of “thin” Mach-O files.
The embedded code signing information obviously
consumes space in the Mach-O file and this space is not
available/reserved in a newly compiled and linked Mach-O file – space
has to be added. There is a program for the Apple Mac called “codesign_allocate”
that extends Mach-O files to make space for a code signature.
Typically, extending a Mach-O file to make space for a signature
involves locating the segment called “__LINKEDIT” (other common segments
are called “__TEXT” and “__DATA”) and extending it to make
space for (an estimate of the size of) the code signature. A Mach-O
“load command” called “LC_CODE_SIGNATURE” is also appended to the other
load commands to record the size and location of the code signature. If
the Mach-O file is “fat” then the individual
“thin” slices must be separately extended and moved within the “fat”
file, and the corresponding sizes and offsets to the “thin” slice
updated.
The space reserved by “codesign_allocate”
is typically large enough to contain a replacement code signature,
which means that it is not normally necessary to repeat the operation
when re-signing an app.
A “C” include file containing definitions of the data structures in a “thin” Mach-O file (“loader.h”) can be found
here, and definitions of the data structures in a “fat” file (“fat.h”) can be found
here. The third (and final) include file (“codesign.h”), containing the code signing data structures themselves, needed to understand the code signing process can be found
here.
The code signature is actually a “superblob” (using a name derived from
codesign.h), which typically contains four “blobs”:
CODEDIRECTORY, REQUIREMENTS, ENTITLEMENTS and SIGNATURE.
Let’s start with the CODEDIRECTORY blob. At the
time of writing, three versions of this data structure are often
encountered (2.00, 2.01 and 2.02). Amongst other things, this blob
contains the globally unique identifier of the app, the
hash algorithm (normally SHA-1), hashes of the pages of the main
executable (up to, but not including, the code signature) and hashes of
other important data structures (e.g. REQUIREMENTS and ENTITLEMENTS
blobs) and files (e.g.
Info.plist and _CodeSignature/CodeResources).
Even if the app is just being resigned with a
“newer” certificate issued to the same subject as the original signer,
it will still be necessary to modify this blob. The reason is rather
convoluted. The signing certificate (in its entirety,
base64 encoded) is included in the “provisioning profile” which is a
PKCS #7 signed data blob, signed by Apple and downloaded from the iOS
Developer Enterprise Program web site; the provisioning profile is
included in the app in a file with the name “embedded.mobileprovision”
(e.g. “Payload/Example.app/embedded.mobileprovision”). A hash (or two) of “embedded.mobileprovision” is included in the file _CodeSignature/CodeResources
(e.g. “Payload/Example.app/_CodeSignature/CodeResources”) and a hash of _CodeSignature/CodeResources is included in
the CODEDIRECTORY blob.
If the app was originally signed by a software
developer and we now wish to re-sign the app for enterprise deployment,
then even more elements of the blob will change (e.g. the globally
unique identifier, the hash of the ENTITLEMENTS blob,
the hash of Info.plist, amongst others). The globally unique identifier in the CODEDIRECTORY blob must match the value of the
CFBundleIdentifier property in Info.plist; the
CFBundleIdentifier property in Info.plist
must be consistent with the application-identifier in the provisioning
profile; the entitlements in the ENTITLEMENTS blob must be consistent
with the entitlements
in the provisioning profile’s “Entitlements” property, etc..
The REQUIREMENTS blob expresses additional
requirements that the application must fulfil in order to be allowed to
run. The requirements are expressed in a language documented in the “Code
Signing Guide” from Apple; the Apple codesign tool (whose source code is available
here) uses
ANTLR to recognize the requirements.
A typical requirement might be:
designated => identifier "com.acme.myapp"
and anchor apple generic and certificate leaf[subject.CN] = "iPhone
Distribution: Acme Corporation" and certificate
1[field.1.2.840.113635.100.6.2.1]
exists
These requirements are
not linked to the provisioning profile, so one has some flexibility
about what requirements to include. The requirements are checked when
the app is loaded, so whatever the requirements
express needs to be true for the signed app. The requirement shown
above is of the form of the default requirement set by the “codesign” tool. “1.2.840.113635.100.6.2.1” is a “marker” certificate extension OID included in the “Apple
Worldwide Developer Relations Certification Authority” certificate.
Apple apps run in a sandbox environment and the
ENTITLEMENTS blob specifies which additional rights/entitlements the app
should be granted. As mentioned earlier, the contents of the
ENTITLEMENTS blob must be “consistent” with the entitlements
in the provisioning profile’s “Entitlements” property. “Consistent” in
this context means that the claimed entitlements should be a subset of
the entitlements granted by the provisioning profile; wildcard values in
the provisioning profile should be replaced
by fully qualified values in the ENTITLEMENTS blob.
The SIGNATURE blob contains a PKCS #7 signed data
“detached signature”, signing the contents of the CODEDIRECTORY blob.
The “signer
infos” element typically includes the full
trust chain of the signing certificate (including the trusted root
certification authority certificate) and sometimes includes a PKCS #9
“signing time” attribute in the set of authenticated
attributes; the signature is not countersigned by a time stamping
service. The signing certificate contains an “Authority Information
Access” extension that contains the URL of an Apple “On-Line Certificate
Status Protocol” (OCSP) server.
Signing a script-based app
If the app does not have a Mach-O binary file as
its “bundle executable” then the various blobs described above are
stored as separate files in the _CodeSignature folder; the folder structure will look like this:
- Payload/Example.app/_CodeSignature/CodeDirectory
- Payload/Example.app/_CodeSignature/CodeRequirements
- Payload/Example.app/_CodeSignature/CodeResources
- Payload/Example.app/_CodeSignature/CodeSignature
Additional steps for “first time” signature
The additional steps involved in signing an app for the first time are the reservation of space in Mach-O executables and computing the list of files to be hashed and their hashes (i.e. generating the file _CodeSignature/CodeResources).
There are built-in (into the codesign tool) “rules” for deciding which files in an app bundle should have their hashes included in
CodeResources, but this can be influenced by creating a property list file containing other inclusion rules (often a file called “ResourceRules.plist” is used for this purpose). To be consistent with the
newest code signing expectations, all “possible” files should have their hashes included in
CodeResources; some files cannot have their hashes included in
CodeResources (e.g. CodeResources itself and other code signing files in the _CodeSignature folder and the bundle executable (if it is in Mach-O format)) because they include
a hash of CodeResources and are consequently modified after
CodeResources has been created.
Magic is important
The information in Mach-O files can be either big
or little endian and Mach-O slices can be 32 or 64 bit. The values of
the magic numbers at the start of many of the Mach-O data structures
allows both of these characteristics to be determined.
The signature blobs contain magic numbers too, so even if the signature
information is stored in separate files in the _CodeSignature folder, the byte ordering needed for the file can be determined.
Run-time validation of an app
The text above describes what “raw” information is
available when making a decision about whether an app should be allowed
to run. The tests that are actually performed depend upon the platform
and the version of the operating system installed
on the device.
A key feature is the inclusion of information signed by Apple (the file “embedded.mobileprovision” containing the provisioning profile) and the link between this information and the “distribution” certificate
used to sign the app bundle.
As an example, in the case of apps signed for
distribution in an enterprise app store, the App ID and/or Team ID
prefixes provide the raw information needed to check (against a database
of installed apps) whether apps from other “enterprises”
are installed on the device. This could be (is?) used to prevent apps
from being sold outside the Apple App Store.
Developing a tool under Windows
The .NET framework contains classes that make
implementing most of the necessary functionality straightforward. For
example, there are classes that allow files in a ZIP archive to be
updated or replaced in-situ and classes that implement
all of the necessary cryptographic operations. The only “unexpected”
difficulty is the manipulation or conversion of binary property list
files: an
authorative specification of this format is difficult to find.
MacOS app bundles are not zip files, they're just folders
ReplyDelete