/* * Copyright (c) 2006 Apple Computer, Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #include #include #include #include #include #include #include #include "SecureDownload.h" #include "SecureDownloadInternal.h" #include "Download.h" static void CheckCFThingForNULL (CFTypeRef theType) { if (theType == NULL) { CFError::throwMe (); } } Download::Download () : mDict (NULL), mURLs (NULL), mName (NULL), mDate (NULL), mHashes (NULL), mNumHashes (0), mCurrentHash (0), mBytesInCurrentDigest (0) { } static void ReleaseIfNotNull (CFTypeRef theThing) { if (theThing != NULL) { CFRelease (theThing); } } Download::~Download () { ReleaseIfNotNull (mDict); } CFArrayRef Download::CopyURLs () { CFRetain (mURLs); return mURLs; } CFStringRef Download::CopyName () { CFRetain (mName); return mName; } CFDateRef Download::CopyDate () { CFRetain (mDate); return mDate; } void Download::GoOrNoGo (SecTrustResultType result) { switch (result) { case kSecTrustResultInvalid: case kSecTrustResultDeny: case kSecTrustResultFatalTrustFailure: case kSecTrustResultOtherError: MacOSError::throwMe (errSecureDownloadInvalidTicket); case kSecTrustResultProceed: return; // we would normally ask for the user's permission in these cases. // we don't in this case, as the Apple signing root had better be // in X509 anchors. I'm leaving this broken out for ease of use // in case we change our minds... case kSecTrustResultConfirm: case kSecTrustResultRecoverableTrustFailure: case kSecTrustResultUnspecified: { MacOSError::throwMe (errSecureDownloadInvalidTicket); } default: break; } } SecPolicyRef Download::GetPolicy () { SecPolicySearchRef search; SecPolicyRef policy; OSStatus result; // get the policy for resource signing result = SecPolicySearchCreate (CSSM_CERT_X_509v3, &CSSMOID_APPLE_TP_RESOURCE_SIGN, NULL, &search); if (result != noErr) { MacOSError::throwMe (result); } result = SecPolicySearchCopyNext (search, &policy); if (result != noErr) { MacOSError::throwMe (result); } CFRelease (search); return policy; } #define SHA256_NAME CFSTR("SHA-256") void Download::ParseTicket (CFDataRef ticket) { // make a propertylist from the ticket CFDictionaryRef mDict = (CFDictionaryRef) _SecureDownloadParseTicketXML (ticket); CheckCFThingForNULL (mDict); CFRetain (mDict); mURLs = (CFArrayRef) CFDictionaryGetValue (mDict, SD_XML_URL); CheckCFThingForNULL (mURLs); // get the download name mName = (CFStringRef) CFDictionaryGetValue (mDict, SD_XML_NAME); CheckCFThingForNULL (mName); // get the download date mDate = (CFDateRef) CFDictionaryGetValue (mDict, SD_XML_CREATED); CheckCFThingForNULL (mDate); // get the download size CFNumberRef number = (CFNumberRef) CFDictionaryGetValue (mDict, SD_XML_SIZE); CFNumberGetValue (number, kCFNumberSInt64Type, &mDownloadSize); // get the verifications dictionary CFDictionaryRef verifications = (CFDictionaryRef) CFDictionaryGetValue (mDict, SD_XML_VERIFICATIONS); // from the verifications dictionary, get the hashing dictionary that we support CFDictionaryRef hashInfo = (CFDictionaryRef) CFDictionaryGetValue (verifications, SHA256_NAME); // from the hashing dictionary, get the sector size number = (CFNumberRef) CFDictionaryGetValue (hashInfo, SD_XML_SECTOR_SIZE); CFNumberGetValue (number, kCFNumberSInt32Type, &mSectorSize); // get the hashes mHashes = (CFDataRef) CFDictionaryGetValue (hashInfo, SD_XML_DIGESTS); CFIndex hashSize = CFDataGetLength (mHashes); mNumHashes = hashSize / CC_SHA256_DIGEST_LENGTH; mDigests = (Sha256Digest*) CFDataGetBytePtr (mHashes); mCurrentHash = 0; mBytesInCurrentDigest = 0; } void Download::Initialize (CFDataRef ticket, SecureDownloadTrustSetupCallback setup, void* setupContext, SecureDownloadTrustEvaluateCallback evaluate, void* evaluateContext) { // decode the ticket SecCmsMessageRef cmsMessage = GetCmsMessageFromData (ticket); // get a policy SecPolicyRef policy = GetPolicy (); // parse the CMS message int contentLevelCount = SecCmsMessageContentLevelCount (cmsMessage); SecCmsSignedDataRef signedData; OSStatus result; int i = 0; while (i < contentLevelCount) { SecCmsContentInfoRef contentInfo = SecCmsMessageContentLevel (cmsMessage, i++); SECOidTag contentTypeTag = SecCmsContentInfoGetContentTypeTag (contentInfo); if (contentTypeTag != SEC_OID_PKCS7_SIGNED_DATA) { continue; } signedData = (SecCmsSignedDataRef) SecCmsContentInfoGetContent (contentInfo); if (signedData == NULL) { MacOSError::throwMe (errSecureDownloadInvalidTicket); } // import the certificates found in the cms message result = SecCmsSignedDataImportCerts (signedData, NULL, certUsageObjectSigner, true); if (result != 0) { MacOSError::throwMe (errSecureDownloadInvalidTicket); } int numberOfSigners = SecCmsSignedDataSignerInfoCount (signedData); int j; if (numberOfSigners == 0) // no signers? This is a possible attack { MacOSError::throwMe (errSecureDownloadInvalidTicket); } for (j = 0; j < numberOfSigners; ++j) { SecTrustResultType resultType; // do basic verification of the message SecTrustRef trustRef; result = SecCmsSignedDataVerifySignerInfo (signedData, j, NULL, policy, &trustRef); // notify the user of the new trust ref if (setup != NULL) { SecureDownloadTrustCallbackResult tcResult = setup (trustRef, setupContext); switch (tcResult) { case kSecureDownloadDoNotEvaluateSigner: continue; case kSecureDownloadFailEvaluation: MacOSError::throwMe (errSecureDownloadInvalidTicket); case kSecureDownloadEvaluateSigner: break; } } if (result != 0) { MacOSError::throwMe (errSecureDownloadInvalidTicket); } result = SecTrustEvaluate (trustRef, &resultType); if (result != noErr) { MacOSError::throwMe (errSecureDownloadInvalidTicket); } if (evaluate != NULL) { resultType = evaluate (trustRef, resultType, evaluateContext); } GoOrNoGo (resultType); } } // extract the message CSSM_DATA_PTR message = SecCmsMessageGetContent (cmsMessage); CFDataRef ticketData = CFDataCreateWithBytesNoCopy (NULL, message->Data, message->Length, kCFAllocatorNull); CheckCFThingForNULL (ticketData); ParseTicket (ticketData); // setup for hashing CC_SHA256_Init (&mSHA256Context); // clean up CFRelease (ticketData); SecCmsMessageDestroy (cmsMessage); } SecCmsMessageRef Download::GetCmsMessageFromData (CFDataRef data) { // setup decoding SecCmsDecoderRef decoderContext; int result = SecCmsDecoderCreate (NULL, NULL, NULL, NULL, NULL, NULL, NULL, &decoderContext); if (result) { MacOSError::throwMe (errSecureDownloadInvalidTicket); } result = SecCmsDecoderUpdate (decoderContext, CFDataGetBytePtr (data), CFDataGetLength (data)); if (result) { SecCmsDecoderDestroy(decoderContext); MacOSError::throwMe (errSecureDownloadInvalidTicket); } SecCmsMessageRef message; result = SecCmsDecoderFinish (decoderContext, &message); if (result) { MacOSError::throwMe (errSecureDownloadInvalidTicket); } return message; } size_t MinSizeT (size_t a, size_t b) { // return the smaller of a and b return a < b ? a : b; } void Download::FinalizeDigestAndCompare () { Sha256Digest digest; CC_SHA256_Final (digest, &mSHA256Context); // make sure we don't overflow the digest buffer if (mCurrentHash >= mNumHashes || memcmp (digest, mDigests[mCurrentHash++], CC_SHA256_DIGEST_LENGTH) != 0) { // Something's really wrong! MacOSError::throwMe (errSecureDownloadInvalidDownload); } // setup for the next receipt of data mBytesInCurrentDigest = 0; CC_SHA256_Init (&mSHA256Context); } void Download::UpdateWithData (CFDataRef data) { // figure out how much data to hash CFIndex dataLength = CFDataGetLength (data); const UInt8* finger = CFDataGetBytePtr (data); while (dataLength > 0) { // figure out how many bytes are left to hash size_t bytesLeftToHash = mSectorSize - mBytesInCurrentDigest; size_t bytesToHash = MinSizeT (bytesLeftToHash, dataLength); // hash the data CC_SHA256_Update (&mSHA256Context, finger, bytesToHash); // update the pointers mBytesInCurrentDigest += bytesToHash; bytesLeftToHash -= bytesToHash; finger += bytesToHash; dataLength -= bytesToHash; if (bytesLeftToHash == 0) // is our digest "full"? { FinalizeDigestAndCompare (); } } } void Download::Finalize () { // are there any bytes left over in the digest? if (mBytesInCurrentDigest != 0) { FinalizeDigestAndCompare (); } if (mCurrentHash != mNumHashes) // check for underflow { MacOSError::throwMe (errSecureDownloadInvalidDownload); } }