#include <CoreFoundation/CoreFoundation.h>
#include <CommonCrypto/CommonDigest.h>
#include <Security/Security.h>
#include <security_utilities/security_utilities.h>
#include <security_cdsa_utilities/cssmbridge.h>
#include <Security/cssmapplePriv.h>
#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;
case kSecTrustResultConfirm:
case kSecTrustResultRecoverableTrustFailure:
case kSecTrustResultUnspecified:
{
MacOSError::throwMe (errSecureDownloadInvalidTicket);
}
default:
break;
}
}
SecPolicyRef Download::GetPolicy ()
{
SecPolicySearchRef search;
SecPolicyRef policy;
OSStatus result;
result = SecPolicySearchCreate (CSSM_CERT_X_509v3, &CSSMOID_APPLE_TP_RESOURCE_SIGN, NULL, &search);
if (result != errSecSuccess)
{
MacOSError::throwMe (result);
}
result = SecPolicySearchCopyNext (search, &policy);
if (result != errSecSuccess)
{
MacOSError::throwMe (result);
}
CFRelease (search);
return policy;
}
#define SHA256_NAME CFSTR("SHA-256")
void Download::ParseTicket (CFDataRef ticket)
{
CFDictionaryRef mDict = (CFDictionaryRef) _SecureDownloadParseTicketXML (ticket);
CheckCFThingForNULL (mDict);
CFRetain (mDict);
mURLs = (CFArrayRef) CFDictionaryGetValue (mDict, SD_XML_URL);
CheckCFThingForNULL (mURLs);
mName = (CFStringRef) CFDictionaryGetValue (mDict, SD_XML_NAME);
CheckCFThingForNULL (mName);
mDate = (CFDateRef) CFDictionaryGetValue (mDict, SD_XML_CREATED);
CheckCFThingForNULL (mDate);
CFNumberRef number = (CFNumberRef) CFDictionaryGetValue (mDict, SD_XML_SIZE);
CFNumberGetValue (number, kCFNumberSInt64Type, &mDownloadSize);
CFDictionaryRef verifications = (CFDictionaryRef) CFDictionaryGetValue (mDict, SD_XML_VERIFICATIONS);
CFDictionaryRef hashInfo = (CFDictionaryRef) CFDictionaryGetValue (verifications, SHA256_NAME);
number = (CFNumberRef) CFDictionaryGetValue (hashInfo, SD_XML_SECTOR_SIZE);
CFNumberGetValue (number, kCFNumberSInt32Type, &mSectorSize);
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)
{
SecCmsMessageRef cmsMessage = GetCmsMessageFromData (ticket);
SecPolicyRef policy = GetPolicy ();
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);
}
result = SecCmsSignedDataImportCerts (signedData, NULL, certUsageObjectSigner, true);
if (result != 0)
{
MacOSError::throwMe (errSecureDownloadInvalidTicket);
}
int numberOfSigners = SecCmsSignedDataSignerInfoCount (signedData);
int j;
if (numberOfSigners == 0) {
MacOSError::throwMe (errSecureDownloadInvalidTicket);
}
for (j = 0; j < numberOfSigners; ++j)
{
SecTrustResultType resultType;
SecTrustRef trustRef;
result = SecCmsSignedDataVerifySignerInfo (signedData, j, NULL, policy, &trustRef);
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 != errSecSuccess)
{
MacOSError::throwMe (errSecureDownloadInvalidTicket);
}
if (evaluate != NULL)
{
resultType = evaluate (trustRef, resultType, evaluateContext);
}
GoOrNoGo (resultType);
}
}
CSSM_DATA_PTR message = SecCmsMessageGetContent (cmsMessage);
CFDataRef ticketData = CFDataCreateWithBytesNoCopy (NULL, message->Data, message->Length, kCFAllocatorNull);
CheckCFThingForNULL (ticketData);
ParseTicket (ticketData);
CC_SHA256_Init (&mSHA256Context);
CFRelease (ticketData);
SecCmsMessageDestroy (cmsMessage);
}
SecCmsMessageRef Download::GetCmsMessageFromData (CFDataRef data)
{
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;
}
static
size_t MinSizeT (size_t a, size_t b)
{
return a < b ? a : b;
}
void Download::FinalizeDigestAndCompare ()
{
Sha256Digest digest;
CC_SHA256_Final (digest, &mSHA256Context);
if (mCurrentHash >= mNumHashes || memcmp (digest, mDigests[mCurrentHash++], CC_SHA256_DIGEST_LENGTH) != 0)
{
MacOSError::throwMe (errSecureDownloadInvalidDownload);
}
mBytesInCurrentDigest = 0;
CC_SHA256_Init (&mSHA256Context);
}
void Download::UpdateWithData (CFDataRef data)
{
CFIndex dataLength = CFDataGetLength (data);
const UInt8* finger = CFDataGetBytePtr (data);
while (dataLength > 0)
{
size_t bytesLeftToHash = mSectorSize - mBytesInCurrentDigest;
size_t bytesToHash = MinSizeT (bytesLeftToHash, dataLength);
CC_SHA256_Update (&mSHA256Context, finger, (CC_LONG)bytesToHash);
mBytesInCurrentDigest += bytesToHash;
bytesLeftToHash -= bytesToHash;
finger += bytesToHash;
dataLength -= bytesToHash;
if (bytesLeftToHash == 0) {
FinalizeDigestAndCompare ();
}
}
}
void Download::Finalize ()
{
if (mBytesInCurrentDigest != 0)
{
FinalizeDigestAndCompare ();
}
if (mCurrentHash != mNumHashes) {
MacOSError::throwMe (errSecureDownloadInvalidDownload);
}
}