Code Signing Overview
Code signing is a security technique that can be used to ensure code integrity, to determine who developed a piece of code, and to determine the purposes for which a developer intended a piece of code to be used. Although the code signing system performs policy checks based on a code signature, it is up to the caller to make policy decisions based on the results of those checks. When it is the operating system that makes the policy checks, whether your code will be allowed to run in a given situation depends on whether you signed the code and on the requirements you included in the signature.
This chapter describes the benefits of signing code and introduces some of the basic concepts you need to understand in order to carry out the code signing process.
Before you read this chapter, you should be familiar with the concepts described in Security Overview.
The Benefits Of Signing Code
When a piece of code has been signed, it is possible to determine reliably whether the code has been modified by someone other than the signer. The system can detect such alternation whether it was intentional (by a malicious attacker, for example) or accidental (as when a file gets corrupted). In addition, through signing, a developer can state that an app update is valid and should be considered by the system as the same app as the previous version.
For example, suppose a user grants the SurfWriter app permission to access a keychain item. Each time SurfWriter attempts to access that item, the system must determine whether it is indeed the same app requesting access. If the app is signed, the system can identify the app with certainty. If the developer updates the app and signs the new version with the same unique identifier, the system recognizes the update as the same app and gives it access without requesting verification from the user. On the other hand, if SurfWriter is corrupted or hacked, the signature no longer matches the previous signature; the system detects the change and refuses access to the keychain item.
Similarly, if you use Parental Controls to prevent your child from running a specific game, and that game has been signed by its manufacturer, your child cannot circumvent the control by renaming or moving files. Parental Controls uses the signature to unambiguously identify the game regardless of its name, location, or version number.
All sorts of code can be signed, including tools, applications, scripts, libraries, plug-ins, and other “code-like” data.
Code signing has three distinct purposes. It can be used to:
ensure that a piece of code has not been altered
identify code as coming from a specific source (a developer or signer)
determine whether code is trustworthy for a specific purpose (for example, to access a keychain item).
To enable signed code to fulfill these purposes, a code signature consists of three parts:
A seal, which is a collection of checksums or hashes of the various parts of the code, such as the identifier, the
Info.plist, the main executable, the resource files, and so on. The seal can be used to detect alterations to the code and to the app identifier.
A digital signature, which signs the seal to guarantee its integrity. The signature includes information that can be used to determine who signed the code and whether the signature is valid.
A unique identifier, which can be used to identify the code or to determine to which groups or categories the code belongs. This identifier can be derived from the contents of the
Info.plistfor the app, or can be provided explicitly by the signer.
For more discussion of digital signatures, see the following section, “Digital Signatures and Signed Code.”
To learn more about how a code signature is used to determine the signed code’s trustworthiness for a specific purpose, see “Code Requirements.”
Note that code signing deals primarily with running code. Although it can be used to ensure the integrity of stored code (on disk, for example), that's a secondary use.
To fully appreciate the uses of code signing, you should be aware of some things that signing cannot do:
It can’t guarantee that a piece of code is free of security vulnerabilities.
It can’t guarantee that an app will not load unsafe or altered code—such as untrusted plug-ins—during execution.
It is not a digital rights management (DRM) or copy protection technology. Although the system could determine that a copy of your app had not been properly signed by you, or that its copy protection had been hacked, thus making the signature invalid, there is nothing to prevent a user from running the app anyway.
Digital Signatures and Signed Code
As explained in Security Overview, a digital signature uses public key cryptography to ensure data integrity. Like a signature written with ink on paper, a digital signature can be used to identify and authenticate the signer. However, a digital signature is more difficult to forge, and goes one step further: it can ensure that the signed data has not been altered. This is somewhat like designing a paper check or money order in such a way that if someone alters the written amount of money, a watermark with the text “Invalid” becomes visible on the paper.
To create a digital signature, the signing software computes a special type of checksum called a hash (or digest) based on a piece of data or code and encrypts that hash with the signer’s private key. This encrypted hash is called a signature.
To verify that signature, the verifying software computes a hash of the data or code. It then uses the signer’s public key to decrypt the signature, thus obtaining the original hash as computed by the signer. If the two hashes match, the data has not been modified since it was signed by someone in possession of the signer’s private key.
Signed code contains several digital signatures:
If the code is universal, the object code for each slice (architecture) is signed separately. This signature is stored within the binary file itself.
Various components of the application bundle (such as the
Info.plistfile, if there is one) are also signed. These signatures are stored in a file called
_CodeSignature/CodeResourceswithin the bundle.
It is up to the system or program that is launching or loading signed code to decide whether to verify the signature and, if it does, to determine how to evaluate the results of that verification. The criteria used to evaluate a code signature are called code requirements. The signer can specify requirements when signing the code; such requirements are referred to as internal requirements. A verifier can read any internal requirements before deciding how to treat signed code. However, it is up to the verifier to decide what requirements to use. For example, Safari could require a plug-in to be signed by Apple in order to be loaded, regardless of whether that plug-in’s signature included internal requirements.
One major purpose of code signatures is to allow the verifier to identify the code (such as a program, plug-in, or script) to determine whether it is the same code the verifier has seen before. The criteria used to make this determination are referred to as the code’s designated requirement. For example, the designated requirement for Apple Mail might be "was signed by Apple and the identifier is
To see how this works in practice, assume the user has granted permission to the Apple Mail application to access a keychain item. The keychain uses Mail’s designated requirement to identify it: the keychain records the identifier (
com.apple.Mail) and the signer of the application (Apple) to identify the program allowed to access the keychain item. Whenever Mail attempts to access this keychain item, the keychain looks at Mail’s signature to make sure that the program has not been corrupted, that the identifier is
com.apple.Mail, and that the program was signed by Apple. If everything checks out, the keychain gives Mail access to the keychain item. When Apple issues a new version of Mail, the new version includes a signature, signed by Apple, that identifies the application as
com.apple.Mail. Therefore, when the user installs the new version of Mail and it attempts to access the keychain item, the keychain recognizes the updated version as the same program and does not prompt the user for verification.
Architecturally, a code requirement is a script, written in a dedicated language, that describes conditions (restrictions) the code must satisfy to be acceptable for some purpose. It is up to you whether to specify internal requirements when you sign code.
The program identifier or the entire designated requirement can be specified by the signer, or can be inferred by the
codesign tool at the time of signing. In the absence of an explicitly specified designated requirement, the
codesign utility typically builds a designated requirement from the name of the program found in its
Info.plist file and the chain of signatures securing the code signature.
Note that validation of signed code against a set of requirements is performed only when the system or some other program needs to determine whether it is safe to trust that code. For example, unsigned code injected into an application through a buffer overflow can still execute because it was not part of the application at launch time. Similarly, an app with an invalid code identifier may still run (depending on policy), but does not get automatic access to keychain items created by previous versions of the app.
The Role of Trust in Code Signing
Trust is determined by policy. A security trust policy determines whether a particular identity should be accepted for allowing something, such as access to a resource or service. Various parts of OS X have different policies, and make this determination differently. For example, a specialized client application might include a set of root certificates that it trusts when communicating with a specific set of servers. However, these root certificates would not be trusted if those same servers were accessed using a web browser.
In much the same way, many parts of OS X (the OS X keychain and parental controls, for example) do not care what entity signed an application; they care only whether the signer has changed since the last time the signature was checked. They use the code signature’s designated requirement for this purpose.
Other parts of OS X constrain acceptable signatures to only those drawn from certificate authorities (root certificates) that are trusted anchors on the system performing the validation. For those checks, the nature of the identity used matters. The Application Firewall is one example of this type of policy. Self-signed identities and self-created certificate authorities do not work for these purposes unless the user has explicitly told the operating system to trust the certificates.
You can modify the code signing polices of OS X with the