Technical Note TN2206

Mac OS X Code Signing In Depth

The purpose of this technote is to provide a more in depth view of code signing. It is intended to expand upon the information given in the Code Signing Guide by supplying a more detailed analysis of the technology. The target audience for this document is Mac OS X developers who have read and presumably understand the information given in the Code Signing Guide but want to learn a bit more.

This document is not meant to be applied to code signing on the iPhone OS, however, viewing the iPhone OS documentation will give a clue as to the similarities.

Code Signing Recap
Self-signed Identities and Self-created Certificate Authorities
Creating a Self-signed Code Signing Certificate using OpenSSL
Troubleshooting
Document Revision History

Code Signing Recap

Code signing is a facility by which developers can assign a digital identity to their programs. Starting with Mac OS X 10.5 Leopard, Apple has provided you with the tools necessary to sign your programs (see the codesign manual pages). The code signing solution on Mac OS X is intended to be completely managed by you. This means that it is up to you to create, or purchase, your code signing certificates (preferably using the certificate assistant found in the Keychain Access Application menu). You are also responsible for maintaining your signing certificates. Normal maintenance issues range from provisioning the certificates, i.e., the policy around supplying them, to updating proper revocation lists. Of course generating conventional policy requirements (see Code Requirements) and coordinating the actual process of signing your code is left up to you.

In short, code signing is a technology that allows you to dictate how validating mechanisms will interpret your code. Code signing does implement policy checks. Policy is set by the specific subsystem carrying out validation, however, any policy decisions are left up to you and your end users in how you interoperate between a specific set of subsystems.

Trust and Code Signing

Trust is determined by policy. A security trust policy determines whether a particular code identity, which is essentially the designated requirement (DR) for the code, should be accepted for allowing something to happen on the system, e.g., access to a resource or service, after testing for validity. Each Mac OS X subsystem has its own policy, and makes this determination separately. Thus, it makes no sense to ask whether code signing trusts a particular signature. You have to ask based on the subsystem, and it is more meaningful to ask whether a specific subsystem trusts your signature.

In general, most subsystems do not care that your identity certificate chain leads to a trusted anchor, however, some do. Additionally, some subsystems track identities and some don't. Subsystem tracking alludes to how the subsystem verifies an identity after the initial policy decision has been acted upon. For a concrete example, below is a list of current subsystems that verify code signatures:

Table 1  Mac OS X subsystems that verify the validity of code.

Subsystem

Function

Initial Policy

Tracking Policy

Leopard Application Firewall

Restrict inbound network access by applications.

Allow if a trusted anchor check succeeds; otherwise prompt the user.

Initial policy decision is verified against the application's DR.

Parental Controls (MCX)

Restrict what applications a managed user can run.

Explicit administrator decision (no code signing involved in the initial decision).

Initial policy decision is verified against the application's DR.

Keychain Access Controls

Controls what applications can do with specific keychain items.

The creating application is automatically trusted with its item, and determines the access policy using code signing requirements.

Free access to the keychain item by the creating application and tracked with its DR (No automatic tracking for custom ACLs).

Developer Tools Access (DTA)

Restrict what programs are allowed to call DTA APIs (task_for_pid, etc.)

A hard-coded trusted anchor check.

None (each request is evaluated by policy).

The above examples also further emphasize the fact that all policy decisions are determined by a specific subsystem and not by code signing itself. In addition, it highlights the diversity in how code signing can be used by a specific subsystem to carry out policy. For instance:

  • DTA doesn't even have a tracking policy. It simply applies a set requirement to every requester without needing to retain any information.

  • Parental controls show that you don't have to even use code signing at all in order to craft a usable policy.

  • Leopard Application Firewall uses code signing for both its initial and tracking policy decisions.

  • The keychain acts on the tracking policy by default but it can also allow arbitrary requirement-bearing ACLs to be added to express arbitrary policies determined by the owner of a specific keychain item.

Many parts of Mac OS X do not care about the identity of the signer. They care only whether the program is validly signed and stable. Stability is determined through the designated requirement (DR) mechanism, and does not depend on the nature of the certificate authority used. The keychain system and parental controls are examples of such usage. Self-signed identities and homemade certificate authorities (CA) work by default for this case.

Other parts of Mac OS X constrain acceptable signatures to only those drawn from certificate authorities that are trusted on the system performing the validation. For those checks, the nature of the identity certificate used does matter. The Leopard Application Firewall is one example of this usage. Self-signed identities and self-created CA will not be verified as being valid for this check unless the verifying system has been told to trust them for Leopard Application Firewall purposes.

Please keep in mind that using a signing identity that is system-wide trusted doesn't automatically mean that it's:

  • a requirement for a signature to be valid.

  • going to be ignored by the majority of subsystems.

  • going to matter only to particular checks within particular subsystems.

Code Requirements

A code requirement is a statement that expresses constraints on a validly signed application. Code signature validation can accept a requirement as input which will then be used to check whether the code is validly signed and satisfies the constraints of the requirement. When signing code, it is not usually necessary to concern yourself with code requirements. They will be managed implicitly. However, you have the ability to explicitly override the default settings to achieve particular effects.

To explicitly test whether a program satisfies a particular requirement, use the -R option to the codesign command; for example:

$admin> # Make a copy of the md5 tool.
$admin> cp /sbin/md5 .
$admin> # The copy still satisifies its DR.
$admin> codesign -vvvv ./md5
./md5: valid on disk
./md5: satisfies its Designated Requirement
$admin> # And we can check that it's signed by Apple.
$admin> codesign -vvvv -R="anchor apple" ./md5
./md5: valid on disk
./md5: satisfies its Designated Requirement
./md5: explicit requirement satisfied
$admin> # Modify the binary.
$admin> chmod u+w ./md5
$admin> dd if=/dev/zero bs=1 count=1 seek=8192 conv=notrunc of=./md5
1+0 records in
1+0 records out
1 bytes transferred in 0.000036 secs (27777 bytes/sec)
$admin> # The modified program no longer satisfies its DR.
$admin> codesign -vvvv ./md5
./md5: code or signature modified
$admin> # But we can resign the modified program with our signature.
$admin> codesign -s my-signing-identity -f ./md5
./md5: replacing existing signature
$admin> # And the modified program now satisfies its DR.
$admin> codesign -vvvv ./md5
./md5: valid on disk
./md5: satisfies its Designated Requirement
$admin> # But not our supplement requirement.
$admin> codesign -vvvv -R="anchor apple" ./md5
./md5: valid on disk
./md5: satisfies its Designated Requirement
test-requirement: failed to satisfy code requirement(s)
$admin>

The requirement language is a set of rules that can be chained together using logical operators ("and", "or", "not", and parentheses to denote precedence) to form a requirement expression. Below is the current list of supported rules and their usage:

Table 2  The requirement language syntax.

Rule Syntax Usage

Description

identifier <string>

The signing identifier is exactly the string given

certificate <slot> = <hash>

The certificate in the certificate chain has a SHA-1 hash as given

certificate <slot> trusted

The certificate is trusted as per Trust Settings API for code signing

certificate <slot> [<key>] = <value>

Some part of the certificate has the given value

info [<key>] = <value>

The Info.plist has a key with the given value

cdhash <hash>

The CodeDirectory's SHA-1 hash is the given value

anchor <string>

The root certificate given by its path

anchor trusted

The certificate chain must lead to a trusted root

anchor apple

The certificate chain must lead to an Apple root

Some important things to keep in mind:

  • When you pass a path of a certificate to set as an anchor in the designated requirement (DR) the DR will automatically be transformed to store only the SHA-1 hash of that certificate. When the policy engine then evaluates the validity of the signed program it uses the stored hash value found in the DR to compare to the actual anchor.

  • The signing identifier is also embedded in the DR and will default to the CFBundleIdentifier found in the Info.plist for convenience if one is not supplied explicitly. The identifier has no meaning as far as code signing is concerned, other than as a means to make DRs unique.

  • You may pass in negative integers as an index into the certificate chain array if you want the searching origin to start with the root certificate rather than the leaf as the origin; the value for the leaf certificate and root certificate are 0 and -1, respectively. The distance for the leaf origin (positive integer) is measured by the absolute value of the integer value passed in. Whereas, the distance for the root origin (negative integer) is measured by the absolute value of 1 + the integer value passed in.

  • To manipulate or experiment with requirements, use the csreq command.

Code Designated Requirement

All signed code has a designated requirement (DR). This requirement states, from the perspective of the developer of the program, what constraints a program needs to satisfy in order to be considered an instance of this program. Obviously, every program should satisfy its own DR, e.g., codesign -vv checks for this. More interestingly, a program's DR should also be satisfied by updates, i.e., new versions of that code, and by nothing else. This is how the Mac OS X code signing policy engine recognizes updates and upgrades.

By default, the system synthesizes a suitable DR for your code when you sign your program. This will work fine in most cases. However, you may specify an explicit DR when signing your program, and there are situations where you should do so.

To see what DR a program has:

$admin> codesign -d -r- /sbin/md5  Executable=/sbin/md5  # designated => identifier "com.apple.md5" and anchor apple $admin>

Look for the line starting with "designated =>". If it is commented out (starts with a "#" mark), it is implicitly generated. If not, it is explicit.

Use the -r option to the codesign command to explicitly specify a DR when signing; for example:

$admin> codesign -vvvv -s my-signing-identity -r="designated => anchor trusted" ~/Desktop/CodesignTest /Users/admin/Desktop/CodesignTest: signed Mach-O thin (i386) [CodesignTest] $admin>

If you do create a DR, you are responsible for crafting a suitable requirement to use. For example below is a DR for specifying to the policy engine that it should check that the signer of the program leads to a trusted anchor on the calling system and that the program's identifier matches the supplied parameter string.

$admin> codesign -vvvv -s my-signing-identity -r="designated => anchor trusted and \
identifier com.foo.bar" ~/Desktop/CodesignTest
/Users/admin/Desktop/CodesignTest: signed Mach-O thin (i386) [CodesignTest]
$admin>

Certificate Validity

By default, the code signing and validation engines accept signatures made with expired certificates. This means that your signed code will not become invalid when your certificate expires. In simple applications, you can continue signing with an expired certificate and Mac OS X will continue to accept this. Code signing will reject signatures made with identities that have been revoked according to standard X.509 processing rules (See RFC 32809 for an example). Revocation check instructions have to be embedded in the certificates you want checked. Revocation checking is a per-user preference found in Keychain Access that is not enabled by default. When revocation checking is configured and enabled the system may still be unable to ascertain revocation status if it is disconnected from the network in which case the validation might succeed instead of fail, i.e., if certificate revocation Keychain Access preferences are set as "Best Attempt" instead of "Require if Cert Indicates".

Self-signed Identities and Self-created Certificate Authorities

Depending on the policy used by the subsystem in question, a self-signed identity can usually be used (your program will reap all the benefits of being signed by it) as long as that identity is set following the respective policy. Obviously, one big downside in using a self-signed identity is that you will never be able to revoke it, however, it may be sufficient for your organization's certificate policy requirements or if you just want to test out the code signing machinery.

If you decide to create your own CA then you specify an explicit DR naming your own anchor certificate:

Listing 1  Creating a DR

$admin> codesign -vvvv -s my-signing-identity -r="designated => anchor rootCert and identifier com.foo.bar" \
~/Desktop/CodesignTest
/Users/admin/Desktop/CodesignTest: signed Mach-O thin (i386) [CodesignTest]
$admin>

By using your own CA you gain the ability to issue new identities at will, since any signing identities issued from your CA will now satisfy the check. You can do this by selecting "Create a Certificate for Someone Else as a Certificate Authority" as option for the Certificate Assistant in Keychain Access. You can also do this with openssl:

$admin> openssl ca -out cert.rsa -config ./openssl.cnf -infiles request.csr

Just as in a custom created CA, if you buy a signing certificate from a commercial CA you'll want to craft a DR that expresses the CA vendor's issuance policies. For instance, every time your CA reissues you a new certificate your identities will change which is something that your DR should take care to handle.

Creating a Self-signed Code Signing Certificate using OpenSSL

If you already have an SSL identity, i.e., a public and private key pair generated through OpenSSL, and you want to use it for code signing then you will need to use something other than the Certificate Assistant. This is because converting an existing OpenSSL digital identity for use with code signing is currently unsupported by the Certificate Assistant. However, you can solve this problem using openssl. The steps that you need to take are:

Troubleshooting

Signing Modifies the Executable

Signing a program will modify its main executable file. There are some situations where this will cause you trouble:

  • If your program has a self-verification mode that detects a change, your code may refuse to run.

  • If you append data to your executable, such appended data may be removed during signing (Mach-O), or may no longer be in the same place relative to the file's end (CFM).

The obvious solution to these problems is to not meddle with your signed program after you've signed it with codesign. If you're modifying the executable or bundle in any way then the code signing validation engine will obviously pick up on that change and act appropriately with the set policy. If you've set your program to do self-integrity checking then it is possible that your preconceived notion of what constitutes "your program" is likely to have changed due to code signing. More specifically, whether you're checking the entire contents of the Mach-O file or just the aggregation of certain pieces of the file it's highly likely that code signing will break what you believe integrity checking is. For example:

$admin> # Let's see what the Mach-O file looks like pre-signing:
$admin> otool -l ~/Desktop/CodesignTest 
CodesignTest:
Load command 0
      cmd LC_SEGMENT
  cmdsize 56
  segname __PAGEZERO
   vmaddr 0x00000000
   vmsize 0x00001000
  fileoff 0
 filesize 0
  maxprot 0x00000000
 initprot 0x00000000
   nsects 0
    flags 0x0
[...]
Load command 11
          cmd LC_LOAD_DYLIB
      cmdsize 52
         name /usr/lib/libSystem.B.dylib (offset 24)
   time stamp 2 Wed Dec 31 16:00:02 1969
      current version 111.0.0
compatibility version 1.0.0
$admin> # Now let's see what it looks like after signing:
$admin> codesign -vvvv -s my-signing-identity ~/Desktop/CodesignTest
CodesignTest: signed Mach-O thin (i386) [CodesignTest]
$admin> otool -l CodesignTest 
CodesignTest:
Load command 0
      cmd LC_SEGMENT
  cmdsize 56
  segname __PAGEZERO
   vmaddr 0x00000000
   vmsize 0x00001000
  fileoff 0
 filesize 0
  maxprot 0x00000000
 initprot 0x00000000
   nsects 0
    flags 0x0
[...]
Load command 11
          cmd LC_LOAD_DYLIB
      cmdsize 52
         name /usr/lib/libSystem.B.dylib (offset 24)
   time stamp 2 Wed Dec 31 16:00:02 1969
      current version 111.0.0
compatibility version 1.0.0
Load command 12
      cmd LC_CODE_SIGNATURE
  cmdsize 16
 dataoff  12816
 datasize 5232
$admin> # Notice the extra load command LC_CODE_SIGNATURE at number 12!
$admin> # Let's check it out even further with pagestuff:
$admin> pagestuff CodesignTest -a
File Page 0 contains Mach-O headers
[...]
File Page 3 contains local of code signature
File Page 4 contains local of code signature
$admin>

Signing Frameworks

Seeing as frameworks are bundles it would seem logical to conclude that you can sign a framework directly. However, this is not the case. To avoid problems when signing frameworks make sure that you sign a specific version as opposed to the whole framework:

$admin> # This is the wrong way:
$admin> codesign -s my-signing-identity ../FooBarBaz.framework
$admin> # This is the right way:
$admin> codesign -s my-signing-identity ../FooBarBaz.framework/Versions/A

Frameworks are "versioned bundles", and each contained version should be signed and validated separately.

Omitting Files from the Bundle's Seal

Your bundle's seal is the CodeResources file that gets generated as part of the code signing process. This file contains a listing of all the files found within your bundle coupled with their respective hash values and a set of rule definitions.

When you sign your program and do not pass in any explicit set of rules then codesign will generate a default set. These default rules, coupled with the hash values, help define what precisely encompasses the seal of your program. In order to tell what exactly got sealed from your program then you can simply check out the CodeResources file:

$admin> cat /Applications/TextEdit.app/Contents/CodeResources
cat /Applications/TextEdit.app/Contents/CodeResources 
<?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>Resources/DocumentWindow.nib/classes.nib</key>
        <dict>
            <key>hash</key>
            <data>
            hw0UoNj0U+XUhBMOhS5Tr5l1PqM=
            </data>
            <key>optional</key>
            <true/>
        </dict>
        [...]
    </dict>
    <key>rules</key>
    <dict>
        <key>^Resources/</key>
        <true/>
        <key>^Resources/.*\.lproj/</key>
        <dict>
            <key>omit</key>
            <true/>
            <key>weight</key>
            <real>10</real>
        </dict>
        [...]
    </dict>
</dict>
</plist>
$admin>

It's entirely possible that you may not find your default seal that was generated to be sufficient. Therefore, if you want to omit or add some files from your bundle's seal you'll want to check out the --resource-rules argument to codesign.

The format of the property list file that you pass by path into the --resource-rules argument is a single dictionary with the key of rules. That single dictionary contains all the rules associated with a specific file inside the bundle of a signed application. Each of these rules is also a dictionary with the key being a standard POSIX basic regular expressions (you may use ^ and/or $ to anchor the match) that is matched against the relative path of a resource file starting at the Contents directory of the bundle.

Currently there are three keys that can be used for the rule definitions:

  • weight (real): Rules are matched against a file in descending order of weight; weights are non-negative and the default weight is zero. The "heaviest", i.e., the largest matching rule applies and if no rules match then the file is not sealed.

  • optional (boolean): If true, the file may be missing and that's fine. However, if it is present it must still be exactly as it was during signing. This key is mutually incompatible with the omit key.

  • omit (boolean): If true, the file is excluded from the seal. (mutually incompatible with optional)

There is a shorthand way of specifying rules: instead of specifying a dictionary as a rule's value, a simple Boolean can be given instead. True means include and false means exclude from the seal.

An example rule definition property list is given below:

Listing 2  An example rule definition

<?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>rules</key>
          <dict>
                    <key>^Resources/</key>
                    <true/>
                    <key>^Resources/.*\.lproj/</key>
                    <dict>
                              <key>omit</key>
                              <true/>
                              <key>weight</key>
                              <real>10</real>
                    </dict>
                    <key>^Plugins/</key>
                    <dict>
                               <key>optional</key>
                               <true/>
                               <key>weight</key>
                               <real>30</real>
                    <dict>
                    <key>^version.plist$</key>
                    <true/>
          </dict>
</dict>
</plist>

The above example illustrates how you would:

  • include the Resources folder of your bundle in the seal.

  • omit the localization bundles from your Resources folder from being added to the seal.

  • allow the Plugins folder to be optional for validation but still included in the seal.

  • include the version property list of your bundle in the seal.

If you're not familiar with regular expressions, the following might be helpful:

  • ^ means to look only at the start of the target string, e.g., ^Moof will not find Moof in "I Moof!" but will find it in "Moof lives!". The ^ character inside a bracket expression, [ ^<expression> ], takes on a different meaning, i.e., to exclude the set representing the expression immediately following it.

  • $ means to look only at the end of the target string, e.g., Moof$ will not find Moof in "Moof lives!" but will find it in "I Moof".

  • . means allow any character(s) in this position, e.g., Moof. will not match "Moof" but will with "Mooff" and "Moof!".

  • * means match the preceding element zero or more times, e.g., Moo*f will not match "Moaaaf" but will with "Moooooof" and "Mof".

  • + means match the preceding element one or more times, e.g., Moo+f will not match "Moaaaf" but will with "Moooooof" and "Mooof".

One thing to keep in mind is that the / character is not special when matching resource paths against regular expressions. So "Moof.*ing" will match "Moofaling" but also "Moof/confusing" and "Moof/more/than/confusing". To express one directory element only, say [^/]+ (a sequence of non-slash characters). The rule "Moof[^/]*ing" would match "Moofaling" but not "Moof/confusing".

Extended Key Usage

Standard X.509 certificates (RFC 2459) contain object identifiers (OID) which form key usage extensions to define what the public and private key can and cannot be used for. Extended key usages just further refine the key usage extensions. An extension is either critical or non-critical. If the extension is critical than the identity must only be used for indicated purpose(s).

The X.509 certificates and their code signing extended key usage is obviously required for an identity to be used for code signing on Mac OS X. However, the code signing extended key usage should also be the only extended key usage for a certificate (this code signing extended usage is critical) in order to be valid to the Mac OS X code signing subsystem. It is not possible to create one certificate that can be used for both code signing and other purposes.

You can check your certificate to find out whether the code signing extended key usage attribute is present by viewing the certificate in Keychain Access or by using any other X.509 compliant certificate parser. Dumping all the available information about a certificate can also show if the certificate has other usages which will cause it to be an invalid identity for use with code signing on Mac OS X. For example:

$admin> # On Mac OS X 10.5 you can use security and certtool
$admin> security find-certificate -a -e clarus@apple.com -p > cert.pem
$admin> certtool d cert.pem
Serial Number : 01 
Issuer Name :
   Common Name : Testing Code Signing
   Org : Apple Inc.
   OrgUnit : DTS
   State : CA
   Country : US
   Locality : Cupertino
   Email addrs : clarus@apple.com
Subject Name :
   Common Name : Testing Code Signing
   Org : Apple Inc.
   OrgUnit : DTS
   State : CA
   Country : US
   Locality : Cupertino
   Email addrs : clarus@apple.com
Cert Sig Algorithm : OID : < 06 09 2A 86 48 86 F7 0D 01 01 05 >
   alg params : 05 00 
Not Before : 19:27:16 Nov 14, 2007
Not After : 19:27:16 Nov 13, 2008
Pub Key Algorithm : OID : < 06 09 2A 86 48 86 F7 0D 01 01 01 >
   alg params : 05 00 
Pub key Bytes : Length 270 bytes : 00 00 00 00 00 00 00 00 ...
CSSM Key :
   Algorithm : RSA
   Key Size : 2048 bits
   Key Use : CSSM_KEYUSE_VERIFY 
Signature : 256 bytes : FF FF FF FF FF FF FF FF ...
Other field: : OID : < 06 0C 60 86 48 01 86 F8 4D 02 01 01 01 17 >
Other field: : OID : < 06 0C 60 86 48 01 86 F8 4D 02 01 01 01 16 >
Extension struct : OID : < 06 03 55 1D 0F >
   Critical : TRUE
   usage : DigitalSignature 
Extension struct : OID : < 06 03 55 1D 25 >
   Critical : TRUE
   purpose  0 : OID : < 06 08 2B 06 01 05 05 07 03 03 >
$admin>

Shipping your Signed Code

Code signing uses extended attributes. If the extended attributes are lost then the program's identity will be break. Thus, when you ship your program, you must use a mechanism that preserves extended attributes.

One way to guarantee preservation of extended attributes is by packing up your signed code in a read-write disk image (DMG) file before signing and then, after signing, converting to read-only. You should also be careful to not move your application from a system later than Mac OS X 10.3 to a system at or earlier than Mac OS X 10.3 and then back to a system later than MAc OS 10.3 again as it will drop the extended attributes following that transport pattern. You probably don't need to use a disk image until the final package stage so another less heavy-handed method would be to use ZIP files.

Mac OS X 10.5 Parental Controls, MCX, and Application Firewall

The Parental Controls, MCX, and Application Firewall subsystems in Leopard, when encountering an unsigned program, will ad hoc sign the program in order to track its identity from launch to launch. This will usually modify the program on disk, and can happen without apparent user input, e.g., when the Application Firewall first notices that your program is trying to accept an inbound network connection. If your program can be damaged by signing, it is imperative that you ship a signed version as soon as practical. Programs signed by their manufacturer are not modified in this way.

Helper tools and other executable extras

If you have bundles that contain helper code, e.g., privileged tools, scripts, plug-ins, libraries, etc., do not put them into the Resources folder of the bundle. Files in the Resource folder are directly sealed to the main executable. Helper tools and libraries should be stored in Contents/MacOS and Contents/Libraries, respectively. Plug-ins should not be stored in the parent bundle's seal. As a result, helper tools, libraries, and plug-ins are all meant to be signed independently.

A suitable place for a helper bundle is the support directory Contents. Use the CFBundleCopyAuxiliaryExecutableURL API function to find code in any of these places.



Document Revision History


DateNotes
2008-08-06

New document that intermediate to expert level overview of code signing that details specific options and gotchas