/ OSX / libsecurity_manifest / lib / Download.cpp
Download.cpp
  1  /*
  2   * Copyright (c) 2006,2011-2014 Apple Inc. All Rights Reserved.
  3   * 
  4   * @APPLE_LICENSE_HEADER_START@
  5   * 
  6   * This file contains Original Code and/or Modifications of Original Code
  7   * as defined in and that are subject to the Apple Public Source License
  8   * Version 2.0 (the 'License'). You may not use this file except in
  9   * compliance with the License. Please obtain a copy of the License at
 10   * http://www.opensource.apple.com/apsl/ and read it before using this
 11   * file.
 12   * 
 13   * The Original Code and all software distributed under the License are
 14   * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 15   * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 16   * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 17   * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 18   * Please see the License for the specific language governing rights and
 19   * limitations under the License.
 20   * 
 21   * @APPLE_LICENSE_HEADER_END@
 22   */
 23  
 24  #include <CoreFoundation/CoreFoundation.h>
 25  #include <CommonCrypto/CommonDigest.h>
 26  
 27  #include <Security/Security.h>
 28  #include <security_utilities/security_utilities.h>
 29  #include <security_cdsa_utilities/cssmbridge.h>
 30  #include <Security/cssmapplePriv.h>
 31  
 32  #include "SecureDownload.h"
 33  #include "SecureDownloadInternal.h"
 34  #include "Download.h"
 35  
 36  
 37  
 38  static void CheckCFThingForNULL (CFTypeRef theType)
 39  {
 40  	if (theType == NULL)
 41  	{
 42  		CFError::throwMe ();
 43  	}
 44  }
 45  
 46  
 47  
 48  Download::Download () : mDict (NULL), mURLs (NULL), mName (NULL), mDate (NULL), mHashes (NULL), mNumHashes (0), mCurrentHash (0), mBytesInCurrentDigest (0)
 49  {
 50  }
 51  
 52  
 53  
 54  static void ReleaseIfNotNull (CFTypeRef theThing)
 55  {
 56  	if (theThing != NULL)
 57  	{
 58  		CFRelease (theThing);
 59  	}
 60  }
 61  
 62  
 63  
 64  Download::~Download ()
 65  {
 66  	ReleaseIfNotNull (mDict);
 67  }
 68  
 69  
 70  
 71  CFArrayRef Download::CopyURLs ()
 72  {
 73  	CFRetain (mURLs);
 74  	return mURLs;
 75  }
 76  
 77  
 78  
 79  CFStringRef Download::CopyName ()
 80  {
 81  	CFRetain (mName);
 82  	return mName;
 83  }
 84  
 85  
 86  
 87  CFDateRef Download::CopyDate ()
 88  {
 89  	CFRetain (mDate);
 90  	return mDate;
 91  }
 92  
 93  
 94  
 95  void Download::GoOrNoGo (SecTrustResultType result)
 96  {
 97  	switch (result)
 98  	{
 99  		case kSecTrustResultInvalid:
100  		case kSecTrustResultDeny:
101  		case kSecTrustResultFatalTrustFailure:
102  		case kSecTrustResultOtherError:
103  			MacOSError::throwMe (errSecureDownloadInvalidTicket);
104  			
105  		case kSecTrustResultProceed:
106  			return;
107  		
108  		// we would normally ask for the user's permission in these cases.
109  		// we don't in this case, as the Apple signing root had better be
110  		// in X509 anchors.  I'm leaving this broken out for ease of use
111  		// in case we change our minds...
112  		case kSecTrustResultRecoverableTrustFailure:
113  		case kSecTrustResultUnspecified:
114  		{
115  			MacOSError::throwMe (errSecureDownloadInvalidTicket);
116  		}
117  		
118  		default:
119  			break;
120  	}
121  }
122  
123  	
124  
125  SecPolicyRef Download::GetPolicy ()
126  {
127  	SecPolicySearchRef search;
128  	SecPolicyRef policy;
129  	OSStatus result;
130  
131  	// get the policy for resource signing
132  	result = SecPolicySearchCreate (CSSM_CERT_X_509v3, &CSSMOID_APPLE_TP_RESOURCE_SIGN, NULL, &search);
133  	if (result != errSecSuccess)
134  	{
135  		MacOSError::throwMe (result);
136  	}
137  	
138  	result = SecPolicySearchCopyNext (search, &policy);
139  	if (result != errSecSuccess)
140  	{
141  		MacOSError::throwMe (result);
142  	}
143  
144  	CFRelease (search);
145  	
146  	return policy;
147  }
148  
149  
150  
151  #define SHA256_NAME CFSTR("SHA-256")
152  
153  void Download::ParseTicket (CFDataRef ticket)
154  {
155  	// make a propertylist from the ticket
156  	CFRef<CFDictionaryRef> mDict = (CFDictionaryRef) _SecureDownloadParseTicketXML (ticket);
157  	CheckCFThingForNULL (mDict);
158  	CFRetain (mDict);
159  	
160  	mURLs = (CFArrayRef) CFDictionaryGetValue (mDict, SD_XML_URL);
161  	CheckCFThingForNULL (mURLs);
162  	
163  	// get the download name
164  	mName = (CFStringRef) CFDictionaryGetValue (mDict, SD_XML_NAME);
165  	CheckCFThingForNULL (mName);
166  
167  	// get the download date
168  	mDate = (CFDateRef) CFDictionaryGetValue (mDict, SD_XML_CREATED);
169  	CheckCFThingForNULL (mDate);
170  	
171  	// get the download size
172  	CFNumberRef number = (CFNumberRef) CFDictionaryGetValue (mDict, SD_XML_SIZE);
173  	CFNumberGetValue (number, kCFNumberSInt64Type, &mDownloadSize);
174  
175  	// get the verifications dictionary
176  	CFDictionaryRef verifications = (CFDictionaryRef) CFDictionaryGetValue (mDict, SD_XML_VERIFICATIONS);
177  	
178  	// from the verifications dictionary, get the hashing dictionary that we support
179  	CFDictionaryRef hashInfo = (CFDictionaryRef) CFDictionaryGetValue (verifications, SHA256_NAME);
180  	
181  	// from the hashing dictionary, get the sector size
182  	number = (CFNumberRef) CFDictionaryGetValue (hashInfo, SD_XML_SECTOR_SIZE);
183  	CFNumberGetValue (number, kCFNumberSInt64Type, &mSectorSize);
184  	
185  	// get the hashes
186  	mHashes = (CFDataRef) CFDictionaryGetValue (hashInfo, SD_XML_DIGESTS);
187  	CFIndex hashSize = CFDataGetLength (mHashes);
188  	mNumHashes = hashSize / CC_SHA256_DIGEST_LENGTH;
189  	mDigests = (Sha256Digest*) CFDataGetBytePtr (mHashes);
190  	mCurrentHash = 0;
191  	mBytesInCurrentDigest = 0;
192  }
193  
194  
195  
196  void Download::Initialize (CFDataRef ticket,
197  						   SecureDownloadTrustSetupCallback setup,
198  						   void* setupContext,
199  						   SecureDownloadTrustEvaluateCallback evaluate,
200  						   void* evaluateContext)
201  {
202  	// decode the ticket
203  	SecCmsMessageRef cmsMessage = GetCmsMessageFromData (ticket);
204  	
205  	// get a policy
206  	SecPolicyRef policy = GetPolicy ();
207  
208  	// parse the CMS message
209  	int contentLevelCount = SecCmsMessageContentLevelCount (cmsMessage);
210  	SecCmsSignedDataRef signedData;
211  
212  	OSStatus result;
213  	
214  	int i = 0;
215  	while (i < contentLevelCount)
216  	{
217  		SecCmsContentInfoRef contentInfo = SecCmsMessageContentLevel (cmsMessage, i++);
218  		SECOidTag contentTypeTag = SecCmsContentInfoGetContentTypeTag (contentInfo);
219  		
220  		if (contentTypeTag != SEC_OID_PKCS7_SIGNED_DATA)
221  		{
222  			continue;
223  		}
224  
225  		signedData = (SecCmsSignedDataRef) SecCmsContentInfoGetContent (contentInfo);
226  		if (signedData == NULL)
227  		{
228  			MacOSError::throwMe (errSecureDownloadInvalidTicket);
229  		}
230  		
231  		// import the certificates found in the cms message
232  		result = SecCmsSignedDataImportCerts (signedData, NULL, certUsageObjectSigner, true);
233  		if (result != 0)
234  		{
235  			MacOSError::throwMe (errSecureDownloadInvalidTicket);
236  		}
237  		
238  		int numberOfSigners = SecCmsSignedDataSignerInfoCount (signedData);
239  		int j;
240  		
241  		if (numberOfSigners == 0) // no signers?  This is a possible attack
242  		{
243  			MacOSError::throwMe (errSecureDownloadInvalidTicket);
244  		}
245  		
246  		for (j = 0; j < numberOfSigners; ++j)
247  		{
248  			SecTrustResultType resultType;
249  			
250  			// do basic verification of the message
251  			SecTrustRef trustRef;
252  			result = SecCmsSignedDataVerifySignerInfo (signedData, j, NULL, policy, &trustRef);
253  			
254  			// notify the user of the new trust ref
255  			if (setup != NULL)
256  			{
257  				SecureDownloadTrustCallbackResult tcResult = setup (trustRef, setupContext);
258  				switch (tcResult)
259  				{
260  					case kSecureDownloadDoNotEvaluateSigner:
261  						continue;
262  					
263  					case kSecureDownloadFailEvaluation:
264  						MacOSError::throwMe (errSecureDownloadInvalidTicket);
265  					
266  					case kSecureDownloadEvaluateSigner:
267  					break;
268  				}
269  			}
270  			
271  			if (result != 0)
272  			{
273  				MacOSError::throwMe (errSecureDownloadInvalidTicket);
274  			}
275  			
276  			result = SecTrustEvaluate (trustRef, &resultType);
277  			if (result != errSecSuccess)
278  			{
279  				MacOSError::throwMe (errSecureDownloadInvalidTicket);
280  			}
281  			
282  			if (evaluate != NULL)
283  			{
284  				resultType = evaluate (trustRef, resultType, evaluateContext);
285  			}
286  			
287  			GoOrNoGo (resultType);
288  		}
289  	}
290  	
291  	// extract the message 
292  	CSSM_DATA_PTR message = SecCmsMessageGetContent (cmsMessage);
293  	CFDataRef ticketData = CFDataCreateWithBytesNoCopy (NULL, message->Data, message->Length, kCFAllocatorNull);
294  	CheckCFThingForNULL (ticketData);
295  	
296  	ParseTicket (ticketData);
297  
298  	// setup for hashing
299  	CC_SHA256_Init (&mSHA256Context);
300  	
301  	// clean up
302  	CFRelease (ticketData);
303  	SecCmsMessageDestroy (cmsMessage);
304  }
305  
306  
307  
308  SecCmsMessageRef Download::GetCmsMessageFromData (CFDataRef data)
309  {
310  	// setup decoding
311  	SecCmsDecoderRef decoderContext;
312  	int result = SecCmsDecoderCreate (NULL, NULL, NULL, NULL, NULL, NULL, NULL, &decoderContext);
313      if (result)
314      {
315  		MacOSError::throwMe (errSecureDownloadInvalidTicket);
316      }
317  
318  	result = SecCmsDecoderUpdate (decoderContext, CFDataGetBytePtr (data), CFDataGetLength (data));
319  	if (result)
320  	{
321          SecCmsDecoderDestroy(decoderContext);
322  		MacOSError::throwMe (errSecureDownloadInvalidTicket);
323  	}
324  
325      SecCmsMessageRef message;
326  	result = SecCmsDecoderFinish (decoderContext, &message);
327      if (result)
328      {
329  		MacOSError::throwMe (errSecureDownloadInvalidTicket);
330      }
331  
332      return message;
333  }
334  
335  
336  static 
337  size_t MinSizeT (size_t a, size_t b)
338  {
339  	// return the smaller of a and b
340  	return a < b ? a : b;
341  }
342  
343  
344  
345  void Download::FinalizeDigestAndCompare ()
346  {
347  	Sha256Digest digest;
348  	CC_SHA256_Final (digest, &mSHA256Context);
349  	
350  	// make sure we don't overflow the digest buffer
351  	if (mCurrentHash >= mNumHashes || memcmp (digest, mDigests[mCurrentHash++], CC_SHA256_DIGEST_LENGTH) != 0)
352  	{
353  		// Something's really wrong!
354  		MacOSError::throwMe (errSecureDownloadInvalidDownload);
355  	}
356  	
357  	// setup for the next receipt of data
358  	mBytesInCurrentDigest = 0;
359  	CC_SHA256_Init (&mSHA256Context);
360  }
361  
362  
363  
364  void Download::UpdateWithData (CFDataRef data)
365  {
366  	// figure out how much data to hash
367  	CFIndex dataLength = CFDataGetLength (data);
368  	const UInt8* finger = CFDataGetBytePtr (data);
369  	
370  	while (dataLength > 0)
371  	{
372  		// figure out how many bytes are left to hash
373  		size_t bytesLeftToHash = mSectorSize - mBytesInCurrentDigest;
374  		size_t bytesToHash = MinSizeT (bytesLeftToHash, dataLength);
375  		
376  		// hash the data
377  		CC_SHA256_Update (&mSHA256Context, finger, (CC_LONG)bytesToHash);
378  		
379  		// update the pointers
380  		mBytesInCurrentDigest += bytesToHash;
381  		bytesLeftToHash -= bytesToHash;
382  		finger += bytesToHash;
383  		dataLength -= bytesToHash;
384  		
385  		if (bytesLeftToHash == 0) // is our digest "full"?
386  		{
387  			FinalizeDigestAndCompare ();
388  		}
389  	}
390  }
391  
392  
393  
394  void Download::Finalize ()
395  {
396  	// are there any bytes left over in the digest?
397  	if (mBytesInCurrentDigest != 0)
398  	{
399  		FinalizeDigestAndCompare ();
400  	}
401  	
402  	if (mCurrentHash != mNumHashes) // check for underflow
403  	{
404  		MacOSError::throwMe (errSecureDownloadInvalidDownload);
405  	}
406  }
407