A few months ago I presented a neat hack at DerbyCon that let you put your own apps on Apple TV. A few days afterwards, the hack stopped working. It’s time I had a follow-up to explain just what happened (and hopefully teach a little about certificate pinning in the process).

First, a quick review: The Apple TV OS has a feature called “Add Site,” through which a developer can add a pointer to a custom application, which will then appear on the Apple TV’s home screen. To enable this feature, a special configuration profile needs to be loaded. The fun part of my talk was showing how one could find exactly what’s needed to make this profile, through some simple disassembly of the Apple TV binary.

A few days before my talk, the “Add Site” button (which had been a blank icon) suddenly had a nice pretty picture on it. I wasn’t sure how Apple made that happen, but presumed there was a simple explanation. And there was: the Apple TV fetches an XML file called “StoreFront” which includes, among other things, descriptions of all the applications on the Apple TV home screen (I’m just going to call it ATV, okay?) To fetch your own copy of this file just use the following command (all one line, naturally):

curl -H "User-Agent: iTunes-AppleTV/5.3 (2; 8GB; dt:11)"
 https://itunes.apple.com/WebObjects/MZStore.woa/wa/storeFront 
| sed 's/&lt;/</g; s/&gt;/>/g'

(Thanks to Aman Gupta, @tmm1, for saving me the trouble of figuring out the User-Agent bit. The sed script at the end is needed because the embedded application list is encoded as a element of the larger file, and would be nearly unreadable with all the HTML entities.)

The pertinent part looks like this:

<dict>
    <key>merchant</key>
    <string>internet-add-site</string>

    <key>enabled</key>
    <string>YES</string>

    <key>menu-title</key>
    <string>Add Site</string>

    <key>menu-icon-url</key>
    <dict>
        <key>720</key>
        <string>http://a1.phobos.apple.com/us/r1000/000/Features/atv/AutumnResources/images/AddSite@720.png</string>

        <key>1080</key>
        <string>http://a1.phobos.apple.com/us/r1000/000/Features/atv/AutumnResources/images/AddSite@1080.png</string>
    </dict>

    <key>menu-icon-url-version</key>
    <string>2.1</string>

    <key>minimum-required-version</key>
    <string>6.0</string>

    <key>preferred-order</key>
    <integer>2147483647</integer>
</dict>

Apparently, the Add Site application is defined just like every other app, though it doesn’t appear unless the right local preference is set. The change they made was to add the menu-icon-url items.

A few days after the talk, the Add Site application disappeared from my ATVs. This was also because of the StoreFront file – in this case, they added a “minimum-required-version” flag, which kept anything that wasn’t ATV 6.0 from seeing the app, even when it’s already been enabled. (ATV versions are one lower than mobile iOS, so ATV 6.0 == iOS 7.0).

Okay, well, let’s upgrade to ATV 6.0 and reload the profile. Easy enough, right? Except that it doesn’t work.

Turns out, Apple also added a profile signature requirement under 6.0 (and thanks again to @tmm1 for coming up with the conclusive proof on this). Where did this happen? And can we work around it, for example, by creating a profile signed by a CA we trust?

Great question! Let’s find out.

Adding a configuration profile to ATV happens in the ManagedConfiguration framework, in particular, the profiled daemon:/System/Library/PrivateFrameworks/ManagedConfiguration.framework/Support/profiled This method: [MCProfile evaluateTrustOfCertificateChain:(id) outIsAllowedToWriteDefaults:(char *)] basically answers the question “Is the profile signed by X allowed to write to the com.apple.frontrow defaults?” The actual check happens in the method: [MCProfileTrustEvaluator sanitizedProfileSignerCertificateChain IsAllowedToWriteDefaults:(id)] I’ll paraphrase the code here:

  policy = SecPolicyCreateConfigurationProfileSigner()
  trust = SecTrustCreateWithCertificates(cert_chain, policy)
  result = SecTrustEvaluate(trust)

  if (result == 4 || result == 1) 
    ALLOWED = TRUE
  else 
    ALLOWED = FALSE

From here out, it gets easy, because it’s documented! First, what does SecPolicyCreateConfigurationProfileSigner do? Google gets us this file: SecPolicyPriv.h.

/*!
 @function SecPolicyCreateConfigurationProfileSigner
 @abstract Check for key usage of digital signature, has a EKU OID of
     1.2.840.113635.100.4.16 and
    roots to Apple Application Integration 2 Certification Authority
*/
SecPolicyRef SecPolicyCreateConfigurationProfileSigner(void);

This tells us that a specific Extended Key Usage (1.2.840.113635.100.4.16) must be present in the certificate used to sign the profile. Check out the formal docs for more information.

Okay, we can easily spoof that, no big deal. What about the second part? How is the “roots to Apple” test checked? If it just says “The issuer has to be called ‘Apple’” then we should be able to fake it out. Let’s look at the actual code (SecPolicy.c) (I consolidated the relevant parts into one block):

static const UInt8 kAppleCASHA1[kSecPolicySHA1Size] = {
    0x61, 0x1E, 0x5B, 0x66, 0x2C, 0x59, 0x3A, 0x08, 0xFF, 0x58,
    0xD1, 0x4A, 0xE2, 0x24, 0x52, 0xD1, 0x98, 0xDF, 0x6C, 0x60
};


static bool SecPolicyAddAppleAnchorOptions(CFMutableDictionaryRef 
  options)
{
    return SecPolicyAddAnchorSHA1Options(options, kAppleCASHA1);
}


SecPolicyRef SecPolicyCreateConfigurationProfileSigner(void)
{
  SecPolicyRef result = NULL;
  CFMutableDictionaryRef options = NULL;
  require(options = CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
                          &kCFTypeDictionaryKeyCallBacks,
                          &kCFTypeDictionaryValueCallBacks), errOut);

  SecPolicyAddBasicX509Options(options);
  SecPolicyAddAppleAnchorOptions(options);

  // Require the profile signing EKU
  add_eku(options, &oidAppleExtendedKeyUsageProfileSigning);

  require(result = SecPolicyCreate(kSecPolicyOIDAppleProfileSigner, 
    options), errOut);

errOut:
  CFReleaseSafe(options);
  return result;
}

This creates a SecPolicyRef object which requires basic X509 stuff, the aforementioned EKU flag, and an Apple anchor. The AddAppleAnchor part, then, is the real key here, and that adds…. a check for a SHA1 hash. Damn! It’s pinned to a specific root CA certificate based on that certificate’s SHA1 hash.

If your ATV is on 6.x, and it doesn’t already have the Add Site profile loaded, then you can’t add it, unless you get a profile signed by Apple. And since the Apple TV program isn’t open to the public, you can’t get that profile. So, we’re pretty much stuck.

Great for Security. Lousy for DIY Apple TV hackers. Right?

Well, there is one workaround: If your ATV is on 5.2 or 5.3, you can still add the profile (you just won’t see the button), and then upgrade to 6.0, and, boom!, the Add Site application is available, and fully functional.

So we still win in the end, right? Not so fast. Apple also introduced a signature test when using the Add Site feature. But since this post is getting a little long already, I’ll save that for next time.