]> git.saurik.com Git - apple/security.git/blobdiff - libsecurity_manifest/lib/Download.cpp
Security-55163.44.tar.gz
[apple/security.git] / libsecurity_manifest / lib / Download.cpp
diff --git a/libsecurity_manifest/lib/Download.cpp b/libsecurity_manifest/lib/Download.cpp
new file mode 100644 (file)
index 0000000..43c91ed
--- /dev/null
@@ -0,0 +1,409 @@
+/*
+ * 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 <CoreFoundation/CoreFoundation.h>
+#include <CommonCrypto/CommonDigest.h>
+
+#include <Security/Security.h>
+#include <security_utilities/security_utilities.h>
+#include <CoreServices/../Frameworks/CarbonCore.framework/Headers/MacErrors.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;
+               
+               // 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);
+       }
+}
+