macOS 10.14.5 beta, Notarization and Stapling Review

Editor’s Note: Once again, this is a moment frozen in time, designed to educate about a passing moment in time. This post is one of a series, so please be sure to read the other posts in this series, and recognize that things are changing constantly.

Related posts

macOS 10.14.5 beta 2: Kernel Extension Notarization, UAMDM, Whitelisting & You

Recap

Last time on this blog, I talked about a new requirement that is present in the early betas of macOS 10.14.5. Kernel Extensions that are installed on a 10.14.4 system that is upgraded to 10.14.5 may not operate correctly if they are not notarized by Apple. In this situation, if the kernel extension is whitelisted (aka UAKEL) by a user-accepted MDM (aka UAMDM), you have nothing to worry about for now. If you’re not using UAKEL and UAMDM, and you are installing kernel extensions that are not signed and notarized by Apple, you’re going to have a bad time. These extensions will not load, and the applications that depend on them will not operate, if they are built and signed after the demarcation date, which is currently 11 March 2019, but may change in the future.

An Example

Recently, DisplayLink released a new version of their kernel extension:

The release notes state:

Software package notarized by Apple as required for macOS 10.14.5 onwards.

DisplayLink Release Notes

However, should one download the software, and inspect it, one might find that things are lacking:

Persephone:Downloads tom$ stapler validate -v DisplayLink\ USB\ Graphics\ Software\ for\ macOS\ 5.1.1.dmg 
Processing: /Users/tom/Downloads/DisplayLink USB Graphics Software for macOS 5.1.1.dmg
Properties are {
    NSURLIsDirectoryKey = 0;
    NSURLIsPackageKey = 0;
    NSURLIsSymbolicLinkKey = 0;
    NSURLLocalizedTypeDescriptionKey = "Disk Image";
    NSURLTypeIdentifierKey = "com.apple.disk-image-udif";
    "_NSURLIsApplicationKey" = 0;
}
Creating synthetic cdHash for unsigned disk image, DisplayLink USB Graphics Software for macOS 5.1.1.dmg. Humanity must endure.
Signing information is {
    cdhashes =     (
        <fd2d35b7 cea70fab 2e22850b 3f39070d a7fa0f52>
    );
    "cdhashes-full" =     {
        2 = <fd2d35b7 cea70fab 2e22850b 3f39070d a7fa0f52 781113f0 7b8686a8 7803c116>;
    };
    cms = <>;
    "digest-algorithm" = 2;
    "digest-algorithms" =     (
        2
    );
    flags = 2;
    format = "disk image";
    identifier = ADHOC;
    "main-executable" = "file:///Users/tom/Downloads/DisplayLink%20USB%20Graphics%20Software%20for%20macOS%205.1.1.dmg";
    source = "explicit detached";
    unique = <fd2d35b7 cea70fab 2e22850b 3f39070d a7fa0f52>;
}
Stored Codesign length: 12 number of blobs: 0
Total Length: 12 Found blobs: 0
DisplayLink USB Graphics Software for macOS 5.1.1.dmg does not have a ticket stapled to it.

Well, they didn’t staple the DMG file, how about the kext itself?

Persephone:Extensions tom$ stapler validate -v DisplayLinkDriver.kext/
Processing: /Library/Extensions/DisplayLinkDriver.kext
Properties are {
    NSURLIsDirectoryKey = 1;
    NSURLIsPackageKey = 1;
    NSURLIsSymbolicLinkKey = 0;
    NSURLLocalizedTypeDescriptionKey = "Kernel Extension";
    NSURLTypeIdentifierKey = "dyn.ah62d4qmuhk2x445ftb4a";
    "_NSURLIsApplicationKey" = 0;
}
Props are {
    cdhash = <c90f6a0c 1076a443 e73cf694 9fe11422 f63f383e>;
    digestAlgorithm = 2;
    flags = 65536;
    secureTimestamp = "2019-04-12 09:34:45 +0000";
    signingId = "com.displaylink.driver.DisplayLinkDriver";
    teamId = 73YQY62QM3;
}
DisplayLinkDriver.kext does not have a ticket stapled to it.

Nope, no joy there, either. How about the package inside the DMG?

Persephone:Extensions tom$ stapler validate -v /Volumes/DisplayLink\ Installer/DisplayLink\ Software\ Installer.pkg 
Processing: /Volumes/DisplayLink Installer/DisplayLink Software Installer.pkg
Properties are {
    NSURLIsDirectoryKey = 0;
    NSURLIsPackageKey = 0;
    NSURLIsSymbolicLinkKey = 0;
    NSURLLocalizedTypeDescriptionKey = "Installer package";
    NSURLTypeIdentifierKey = "com.apple.installer-package-archive";
    "_NSURLIsApplicationKey" = 0;
}
Sig Type is RSA. Length is 3
Sig Type is CMS. Length is 3
Package DisplayLink Software Installer.pkg uses a checksum of size 20
We do not know how to deal with trailer version 41376. Exepected 1
DisplayLink Software Installer.pkg does not have a ticket stapled to it.

Well, if they notarized any of the parts, they didn’t actually complete the process in a way that allows us to verify the process offline.

When I ran the installer package on my machine, I did receive a UAKEL alert during install that indicates that the payload was being blocked until I accepted the kext, which means that the kext was notarized, just not stapled.

So, what would lead a developer to think that they have notarized their kernel extension successfully, but the operating system would believe otherwise? I can’t be sure of what happened in DisplayLink’s case, but there’s a possibility that it was built on an airgapped system where Xcode could compile the code, and then when it was submitted to Apple for signing and notarization, the final step of stapling the returned ticket to the application was not completed. If the ticket isn’t stapled, Gatekeeper will recognize the unstapled object, because Gatekeeper can talk with Apple and ask for a check based on other factors.

Apple’s Developer Documentation says:

Notarization produces a ticket that tells Gatekeeper that your app is notarized. After notarization completes successfully, the next time any user attempts to run your app on macOS 10.14 or later, Gatekeeper finds the ticket online. This includes users who downloaded your app before notarization.

So, if you deliver an unstapled object, as DisplayLink has, it may still pass muster, but that requires your machine to be able to talk with Apple at the time of install. If you are operating a network which embraces 802.1X user certificates, and you install software at the login window (with Munki, say) you may run into a circumstance where the software is actually notarized by Apple, but without that stapled ticket, you’re stuck if you can’t talk to Apple to prove it. This will result in a failed install.

So, Who Do You Need To Talk To?

According to Apple:

In addition, stapler uses CloudKit to download tickets, which requires access to the following IP address ranges, all on port 443:
17.248.128.0/18
17.250.64.0/18
17.248.192.0/19

If you can’t open up your network to those segments, consider that failure to do so will mean you cannot run what you need to run to make your Mac endpoints successful.

So, What Can I Do?

Well, you might be able to try stapling on your own. If it’s been validated by Apple during a notarization process, but the distributed resources are unstapled, you may be able to “fix” that by trying to staple the necessary objects yourself. They’re notarized, after all, just not by you! You can attempt this yourself.

xcrun stapler staple /path/to/DisplayLinkDriver.pkg

This results in a different result when you review the dmg file:

Persephone:Extensions tom$ stapler validate -v ~/Desktop/DisplayLink\ Software\ Installer.pkg 
Processing: /Users/tom/Desktop/DisplayLink Software Installer.pkg
Properties are {
    NSURLIsDirectoryKey = 0;
    NSURLIsPackageKey = 0;
    NSURLIsSymbolicLinkKey = 0;
    NSURLLocalizedTypeDescriptionKey = "Installer package";
    NSURLTypeIdentifierKey = "com.apple.installer-package-archive";
    "_NSURLIsApplicationKey" = 0;
}
Sig Type is RSA. Length is 3
Sig Type is CMS. Length is 3
Package DisplayLink Software Installer.pkg uses a checksum of size 20
Terminator Trailer size must be 0, not 2073
{magic: t8lr, version: 1, type: 2, length: 2073}
Found expected ticket at 7812133 with length of 2073
JSON Data is {
    records =     (
                {
            recordName = "2/1/5362032c46062ca6e74bab1bf6ce672f6a578989";
        }
    );
}
 Headers: {
    "Content-Type" = "application/json";
}
Domain is api.apple-cloudkit.com
Response is <NSHTTPURLResponse: 0x7f85265134a0> { URL: https://api.apple-cloudkit.com/database/1/com.apple.gk.ticket-delivery/production/public/records/lookup } { Status Code: 200, Headers {
    "Apple-Originating-System" =     (
        UnknownOriginatingSystem
    );
    Connection =     (
        "keep-alive"
    );
    "Content-Encoding" =     (
        gzip
    );
    "Content-Type" =     (
        "application/json; charset=UTF-8"
    );
    Date =     (
        "Thu, 18 Apr 2019 20:23:51 GMT"
    );
    Server =     (
        "AppleHttpServer/70a91026"
    );
    "Strict-Transport-Security" =     (
        "max-age=31536000; includeSubDomains;"
    );
    "Transfer-Encoding" =     (
        Identity
    );
    Via =     (
        "xrail:st13p00ic-zteu25223401.me.com:8301:18H164:grp60",
        "icloudedge:sv05p01ic-ztde010811:7401:19RC85:San Jose"
    );
    "X-Apple-CloudKit-Version" =     (
        "1.0"
    );
    "X-Apple-Request-UUID" =     (
        "95f1738a-0da3-441e-abe4-982d57970d51"
    );
    "X-Responding-Instance" =     (
        "ckdatabasews:16302401:st42p63ic-ztfb18181201:8201:1906B425:3cafa700202"
    );
    "access-control-expose-headers" =     (
        "X-Apple-Request-UUID, X-Responding-Instance",
        Via
    );
    "apple-seq" =     (
        0
    );
    "apple-tk" =     (
        false
    );
} }
Size of data is 3377
JSON Response is: {
    records =     (
                {
            created =             {
                deviceID = 2;
                timestamp = 1555062296808;
                userRecordName = "_d28c74d190a3782e89496b0a13437fef";
            };
            deleted = 0;
            fields =             {
                signedTicket =                 {
                    type = BYTES;
                    value = "snipped for simplicity.";
                };
            };
            modified =             {
                deviceID = 2;
                timestamp = 1555062296808;
                userRecordName = "_d28c74d190a3782e89496b0a13437fef";
            };
            pluginFields =             {
            };
            recordChangeTag = judvxvj5;
            recordName = "2/1/5362032c46062ca6e74bab1bf6ce672f6a578989";
            recordType = DeveloperIDTicket;
        }
    );
}
Downloaded ticket has been stored at file:///var/folders/tk/qhvvt21x7z3fzt125dpgjlym0000gp/T/95f1738a-0da3-441e-abe4-982d57970d51.ticket.
The validate action worked!

This will mean that, as admins, if we want to install notarized software in a circumstance where network access won’t permit a conversation with the Apple CloudKit servers, you’re going to want to make sure the notarization ticket is stapled to the installer. This may require changes to our workflows, and now’s a good time to start thinking about what that will mean for automatic download and interpretations of installers.

Thanks as always to the gang from #notarization on the Mac Admins Slack for providing good discussion of a difficult topic.