Skip to content

Instantly share code, notes, and snippets.

@arubdesu
Last active September 24, 2015 20:33
Show Gist options
  • Save arubdesu/c246b2438329156f69ed to your computer and use it in GitHub Desktop.
Save arubdesu/c246b2438329156f69ed to your computer and use it in GitHub Desktop.
New page for under the Autopkg->Recipes wiki heading

###Using CodeSignatureVerification in your recipe

The CodeSignatureVerifier processor was added to allow signature verification for both:

  1. Installer packages (.pkg or .mpkg).

  2. Application bundles (.app). This option is necessary since not all software is supplied as packages. Instead, software is commonly released for download 'bare' at the root of a zip archive. CodeSignatureVerifier can look inside a DMG mount, but zip's must use the Unarchiver processor first.

Take into account the fact that both the computer running autopkg and the one that genrates the required configuration settings should have the default spctl settings, meaning Gatekeeper allows apps from the App Store and those signed with an Apple-recognized Developer ID. ####Adding Application bundle (.app) verification to your recipe

For drag-drop apps downloaded as zip archives, you'd run codesign --display -r- --deep -v /path/to/.appBundle and paste the entire output of the designated key into the format below (with the precursor step of adding the Unarchiver processor as necessary). Make sure that there is Sealed Resources version=2 in the output, to verify that the developer signed it with the second version of Apple's signature format, as that is what the processor is strict about when checking.

Also notice the processors placement after an EndOfCheckPhase step below, so that folks who run autopkg in 'check-only' mode won't needlessly unarchive the app and verify its contents every time the recipe runs.

<dict>
    <key>Processor</key>
    <string>EndOfCheckPhase</string>
</dict>
<dict>
    <key>Processor</key>
    <string>Unarchiver</string>
    <key>Arguments</key>
    <dict>
        <key>archive_path</key>
        <string>%pathname%</string>
        <key>destination_path</key>
        <string>%RECIPE_CACHE_DIR%/%NAME%</string>
        <key>purge_destination</key>
        <true/>
    </dict>
</dict>
<dict>
    <key>Processor</key>
    <string>CodeSignatureVerifier</string>
    <key>Arguments</key>
    <dict>
        <key>input_path</key>
        <string>%RECIPE_CACHE_DIR%/%NAME%/TextExpander.app</string>
        <key>requirement</key>
        <string>anchor apple generic and identifier "com.smileonmymac.textexpander" and (certificate leaf[field.1.2.840.113635.100.6.1.9] /* exists */ or certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.OU] = "7PKJ6G4DXL")</string>
    </dict>
</dict>

Please note that even getting valid output from one part of an application does not guarantee all resources that are part of it will pass verification, as even Google Chrome has a outstanding issue with how Apple's signing works.

####Adding package installer (.pkg) verification to your recipe To check for a valid signature from a package, run this command: pkgutil --check-signature /path/to/pkg and fit it into this format (assuming, as in the example, it's pulled down from the vendor in a DMG, and therefore the pathname can essentially point to its mount):

<dict>   
    <key>Processor</key>   
    <string>CodeSignatureVerifier</string>   
    <key>Arguments</key>   
    <dict>   
        <key>input_path</key>   
        <string>%pathname%/Office*.*pkg</string>
        <key>expected_authority_names</key>
        <array>
            <string>Developer ID Installer: Microsoft Corporation</string>
            <string>Developer ID Certification Authority</string>
            <string>Apple Root CA</string>
        </array>
    </dict>
</dict>

###How Application bundle (.app) verification works

CodeSignatureVerifier key: requirement

Apple binary utility used: codesign

What Autopkg does:

  1. Verifies the developer ID cert was issued by Apple.

  2. Commonly, also verifies a specific developer ID by checking the value in the OU field. This prevents an attacker from tricking the user into trusting it because it's signed by another developer's cert instead of the actual expected author.

Code signature verification for .app bundles uses the codesign tool, which first uses 'deep' verification to ensure all resources that make up the app are signed with [version 2 signatures(http://indiestack.com/2014/10/gatekeepers-opaque-whitelist/). This does not guarantee that trust will be evaluated, as no system policies are run within the tool itself to consult a chain of trust. (The requirement itself could be bypassed along with Gatekeeper, although it should cause failures instead of false-positives.) Then it looks at the provided authority names to make sure they are as expected.

You can use codesign to evaluate self-signed signatures given a proper requirement string, which is what the CodeSignatureVerifier processor does when fed an app bundle. If there's no requirement string, CodeSignatureVerifier verifies the bundle against it's own designated requirement (which would allow it to succeed for specific Developer ID-signed apps with no OU designation). In most cases the requirement string should be copied by a recipe author from the apps designated requirement. Let's break down one of these requirement strings:

anchor apple generic and identifier "com.hjuutilainen.MunkiAdmin" and (certificate leaf[field.1.2.840.113635.100.6.1.9] /* exists */ or certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[ field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.OU] = "8XXWJ76X9Y")

  • anchor apple generic means that the code must be signed by Apple, including code signed using a signing certificate issued by Apple to other developers.
  • identifier "com.hjuutilainen.MunkiAdmin" means that the unique identifier string embedded in the code signature is exactly equal to "com.hjuutilainen.MunkiAdmin".
  • certificate leaf[field.1.2.840.113635.100.6.1.9] means that the leaf (the private Developer ID certificate) must have a particular field present.
  • certificate leaf[subject.OU] = "8XXWJ76X9Y" means that the leaf must have a specific subject value of "8XXWJ76X9Y" in the Organizational Unit field.

##How installer package (.pkg) verification works

CodeSignatureVerifier key: expected_authority_names

Apple binary utility used: pkgutil with the --check-signature flag

What Autopkg does:

  1. Checks that the package is signed by a root cert which is trusted by the System root store.

  2. Checks the expected_authority_names array to make sure that the specific issuers we expect were used.

Installer package verification uses the pkgutil tool, which verifies that the item hasn't been tampered with and if it is trusted by the system as correctly signed with Apple at the root of trust. This check is done first and it must pass. Only if the package is valid and trusted with Apple as the root is the expected_authority_names array used and matched against the strings we'd get back after running pkgutil.

@jesseendahl
Copy link

This looks great! Only feedback I have is that there should probably be a section titled "How verification works" which explains why there are two different options (expected_authority_names, required), and exactly what's being verified when you use each of them.

https://github.com/autopkg/autopkg/wiki/Processor-CodeSignatureVerifier#input-variables

@hjuutilainen mentioned on autopkg/autopkg#114 that using "required" with the output of "Should be a far more reliable way for verification than using the expected_authority_names" but didn't include much other context.

It probably also makes sense to expand on your comment about how signing changed with "the second version of signing Apple required late in the Mavericks release cycle" and move it to this new "How verification works" section, since I doubt many people are familiar with what changed, or even realize that a change occurred.

@hjuutilainen
Copy link

Yes, "How verification works" section would be needed in my opinion too.

Installer package signature verification and application bundle code signature verification are two different beasts. It was my poor design choice to not separate these two in their own processors. Here's some more detailed description about what CodeSignatureVerifier is doing behind the scenes:

Code signature verification for .app bundles uses codesign tool which verifies that the item is correctly signed and hasn't been tampered with. It does not (in its default form) evaluate trust. This means that you can easily use codesign to evaluate self-signed signatures given a proper requirement string. The CodeSignatureVerifier processor, when given an app bundle, uses the requirement string to evaluate the validity. If there's no requirement string, CodeSignatureVerifier verifies the bundle against it's own designated requirement. In most cases the requirement string should be copied by a recipe author from the apps designated requirement. Let's tear one of these requirements apart:

anchor apple generic and identifier "com.hjuutilainen.MunkiAdmin" and (certificate leaf[field.1.2.840.113635.100.6.1.9] /* exists */ or certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.OU] = "8XXWJ76X9Y")
  • anchor apple generic means that the code must be signed by Apple, including code signed using a signing certificate issued by Apple to other developers.
  • identifier "com.hjuutilainen.MunkiAdmin" means that the unique identifier string embedded in the code signature is exactly equal to "com.hjuutilainen.MunkiAdmin".
  • certificate leaf[field.1.2.840.113635.100.6.1.9] means that the leaf (the private Developer ID certificate) must have a particular field present.
  • certificate leaf[subject.OU] = "8XXWJ76X9Y" means that the leaf must have a specific subject value of "8XXWJ76X9Y" in the Organizational Unit field.

As you can see, there's no trust involved here. It's just all about validity and the signing certificate properties. In my opinion, the trust evaluation isn't even needed/wanted here because we only want to make sure the item is what we're expecting it to be. The code signing requirements are a perfect way to verify this.

Installer package verification uses pkgutil tool which verifies that the item is correctly signed, hasn't been tampered with and also checks if it is trusted by the system. This check is done first and it must pass. Only if the package is valid and trusted, the expected_authority_names array is used and matched against the strings we got back from the validation. It would be great if we could use the same requirements language when verifying installer packages but I haven't found a way to do this.

One other thing I'd like to add is that recipe authors should also care what happens after signature verification. For example, a recipe downloads a disk image and verifies the signature of <dmg>/foo.app. It then goes on and feeds the disk image to munkiimport which finds and imports a malicious <dmg>/bar.app to the repo. I think CodeSignatureVerifier could declare a verified_path output variable which could then be used in the remaining steps of the recipe.

@jesseendahl
Copy link

@hjuutilainen Thanks for the excellent explanation! I like the idea of verified_path =) And thanks for taking the time to chat about all this with me on Slack.

@arubdesu @hjuutilainen I did some work to break this stuff into separate sections and clarify the difference between app and pkg signing verification. Can you guys take a look and see if it makes sense https://gist.github.com/jesseendahl/72cdb8bb1941c98ccbcf

@jesseendahl
Copy link

@timsutton
Copy link

Thank you. I will take a look in greater detail soon, but it's much better that this now written more objectively as documentation. Thanks to Hannes and Jesse for their input as well.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment