1/*
2 * Copyright (c) 2006 Apple Computer, 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
38static void CheckCFThingForNULL (CFTypeRef theType)
39{
40	if (theType == NULL)
41	{
42		CFError::throwMe ();
43	}
44}
45
46
47
48Download::Download () : mDict (NULL), mURLs (NULL), mName (NULL), mDate (NULL), mHashes (NULL), mNumHashes (0), mCurrentHash (0), mBytesInCurrentDigest (0)
49{
50}
51
52
53
54static void ReleaseIfNotNull (CFTypeRef theThing)
55{
56	if (theThing != NULL)
57	{
58		CFRelease (theThing);
59	}
60}
61
62
63
64Download::~Download ()
65{
66	ReleaseIfNotNull (mDict);
67}
68
69
70
71CFArrayRef Download::CopyURLs ()
72{
73	CFRetain (mURLs);
74	return mURLs;
75}
76
77
78
79CFStringRef Download::CopyName ()
80{
81	CFRetain (mName);
82	return mName;
83}
84
85
86
87CFDateRef Download::CopyDate ()
88{
89	CFRetain (mDate);
90	return mDate;
91}
92
93
94
95void 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 kSecTrustResultConfirm:
113		case kSecTrustResultRecoverableTrustFailure:
114		case kSecTrustResultUnspecified:
115		{
116			MacOSError::throwMe (errSecureDownloadInvalidTicket);
117		}
118
119		default:
120			break;
121	}
122}
123
124
125
126SecPolicyRef Download::GetPolicy ()
127{
128	SecPolicySearchRef search;
129	SecPolicyRef policy;
130	OSStatus result;
131
132	// get the policy for resource signing
133	result = SecPolicySearchCreate (CSSM_CERT_X_509v3, &CSSMOID_APPLE_TP_RESOURCE_SIGN, NULL, &search);
134	if (result != errSecSuccess)
135	{
136		MacOSError::throwMe (result);
137	}
138
139	result = SecPolicySearchCopyNext (search, &policy);
140	if (result != errSecSuccess)
141	{
142		MacOSError::throwMe (result);
143	}
144
145	CFRelease (search);
146
147	return policy;
148}
149
150
151
152#define SHA256_NAME CFSTR("SHA-256")
153
154void Download::ParseTicket (CFDataRef ticket)
155{
156	// make a propertylist from the ticket
157	CFDictionaryRef mDict = (CFDictionaryRef) _SecureDownloadParseTicketXML (ticket);
158	CheckCFThingForNULL (mDict);
159	CFRetain (mDict);
160
161	mURLs = (CFArrayRef) CFDictionaryGetValue (mDict, SD_XML_URL);
162	CheckCFThingForNULL (mURLs);
163
164	// get the download name
165	mName = (CFStringRef) CFDictionaryGetValue (mDict, SD_XML_NAME);
166	CheckCFThingForNULL (mName);
167
168	// get the download date
169	mDate = (CFDateRef) CFDictionaryGetValue (mDict, SD_XML_CREATED);
170	CheckCFThingForNULL (mDate);
171
172	// get the download size
173	CFNumberRef number = (CFNumberRef) CFDictionaryGetValue (mDict, SD_XML_SIZE);
174	CFNumberGetValue (number, kCFNumberSInt64Type, &mDownloadSize);
175
176	// get the verifications dictionary
177	CFDictionaryRef verifications = (CFDictionaryRef) CFDictionaryGetValue (mDict, SD_XML_VERIFICATIONS);
178
179	// from the verifications dictionary, get the hashing dictionary that we support
180	CFDictionaryRef hashInfo = (CFDictionaryRef) CFDictionaryGetValue (verifications, SHA256_NAME);
181
182	// from the hashing dictionary, get the sector size
183	number = (CFNumberRef) CFDictionaryGetValue (hashInfo, SD_XML_SECTOR_SIZE);
184	CFNumberGetValue (number, kCFNumberSInt32Type, &mSectorSize);
185
186	// get the hashes
187	mHashes = (CFDataRef) CFDictionaryGetValue (hashInfo, SD_XML_DIGESTS);
188	CFIndex hashSize = CFDataGetLength (mHashes);
189	mNumHashes = hashSize / CC_SHA256_DIGEST_LENGTH;
190	mDigests = (Sha256Digest*) CFDataGetBytePtr (mHashes);
191	mCurrentHash = 0;
192	mBytesInCurrentDigest = 0;
193}
194
195
196
197void Download::Initialize (CFDataRef ticket,
198						   SecureDownloadTrustSetupCallback setup,
199						   void* setupContext,
200						   SecureDownloadTrustEvaluateCallback evaluate,
201						   void* evaluateContext)
202{
203	// decode the ticket
204	SecCmsMessageRef cmsMessage = GetCmsMessageFromData (ticket);
205
206	// get a policy
207	SecPolicyRef policy = GetPolicy ();
208
209	// parse the CMS message
210	int contentLevelCount = SecCmsMessageContentLevelCount (cmsMessage);
211	SecCmsSignedDataRef signedData;
212
213	OSStatus result;
214
215	int i = 0;
216	while (i < contentLevelCount)
217	{
218		SecCmsContentInfoRef contentInfo = SecCmsMessageContentLevel (cmsMessage, i++);
219		SECOidTag contentTypeTag = SecCmsContentInfoGetContentTypeTag (contentInfo);
220
221		if (contentTypeTag != SEC_OID_PKCS7_SIGNED_DATA)
222		{
223			continue;
224		}
225
226		signedData = (SecCmsSignedDataRef) SecCmsContentInfoGetContent (contentInfo);
227		if (signedData == NULL)
228		{
229			MacOSError::throwMe (errSecureDownloadInvalidTicket);
230		}
231
232		// import the certificates found in the cms message
233		result = SecCmsSignedDataImportCerts (signedData, NULL, certUsageObjectSigner, true);
234		if (result != 0)
235		{
236			MacOSError::throwMe (errSecureDownloadInvalidTicket);
237		}
238
239		int numberOfSigners = SecCmsSignedDataSignerInfoCount (signedData);
240		int j;
241
242		if (numberOfSigners == 0) // no signers?  This is a possible attack
243		{
244			MacOSError::throwMe (errSecureDownloadInvalidTicket);
245		}
246
247		for (j = 0; j < numberOfSigners; ++j)
248		{
249			SecTrustResultType resultType;
250
251			// do basic verification of the message
252			SecTrustRef trustRef;
253			result = SecCmsSignedDataVerifySignerInfo (signedData, j, NULL, policy, &trustRef);
254
255			// notify the user of the new trust ref
256			if (setup != NULL)
257			{
258				SecureDownloadTrustCallbackResult tcResult = setup (trustRef, setupContext);
259				switch (tcResult)
260				{
261					case kSecureDownloadDoNotEvaluateSigner:
262						continue;
263
264					case kSecureDownloadFailEvaluation:
265						MacOSError::throwMe (errSecureDownloadInvalidTicket);
266
267					case kSecureDownloadEvaluateSigner:
268					break;
269				}
270			}
271
272			if (result != 0)
273			{
274				MacOSError::throwMe (errSecureDownloadInvalidTicket);
275			}
276
277			result = SecTrustEvaluate (trustRef, &resultType);
278			if (result != errSecSuccess)
279			{
280				MacOSError::throwMe (errSecureDownloadInvalidTicket);
281			}
282
283			if (evaluate != NULL)
284			{
285				resultType = evaluate (trustRef, resultType, evaluateContext);
286			}
287
288			GoOrNoGo (resultType);
289		}
290	}
291
292	// extract the message
293	CSSM_DATA_PTR message = SecCmsMessageGetContent (cmsMessage);
294	CFDataRef ticketData = CFDataCreateWithBytesNoCopy (NULL, message->Data, message->Length, kCFAllocatorNull);
295	CheckCFThingForNULL (ticketData);
296
297	ParseTicket (ticketData);
298
299	// setup for hashing
300	CC_SHA256_Init (&mSHA256Context);
301
302	// clean up
303	CFRelease (ticketData);
304	SecCmsMessageDestroy (cmsMessage);
305}
306
307
308
309SecCmsMessageRef Download::GetCmsMessageFromData (CFDataRef data)
310{
311	// setup decoding
312	SecCmsDecoderRef decoderContext;
313	int result = SecCmsDecoderCreate (NULL, NULL, NULL, NULL, NULL, NULL, NULL, &decoderContext);
314    if (result)
315    {
316		MacOSError::throwMe (errSecureDownloadInvalidTicket);
317    }
318
319	result = SecCmsDecoderUpdate (decoderContext, CFDataGetBytePtr (data), CFDataGetLength (data));
320	if (result)
321	{
322        SecCmsDecoderDestroy(decoderContext);
323		MacOSError::throwMe (errSecureDownloadInvalidTicket);
324	}
325
326    SecCmsMessageRef message;
327	result = SecCmsDecoderFinish (decoderContext, &message);
328    if (result)
329    {
330		MacOSError::throwMe (errSecureDownloadInvalidTicket);
331    }
332
333    return message;
334}
335
336
337static
338size_t MinSizeT (size_t a, size_t b)
339{
340	// return the smaller of a and b
341	return a < b ? a : b;
342}
343
344
345
346void Download::FinalizeDigestAndCompare ()
347{
348	Sha256Digest digest;
349	CC_SHA256_Final (digest, &mSHA256Context);
350
351	// make sure we don't overflow the digest buffer
352	if (mCurrentHash >= mNumHashes || memcmp (digest, mDigests[mCurrentHash++], CC_SHA256_DIGEST_LENGTH) != 0)
353	{
354		// Something's really wrong!
355		MacOSError::throwMe (errSecureDownloadInvalidDownload);
356	}
357
358	// setup for the next receipt of data
359	mBytesInCurrentDigest = 0;
360	CC_SHA256_Init (&mSHA256Context);
361}
362
363
364
365void Download::UpdateWithData (CFDataRef data)
366{
367	// figure out how much data to hash
368	CFIndex dataLength = CFDataGetLength (data);
369	const UInt8* finger = CFDataGetBytePtr (data);
370
371	while (dataLength > 0)
372	{
373		// figure out how many bytes are left to hash
374		size_t bytesLeftToHash = mSectorSize - mBytesInCurrentDigest;
375		size_t bytesToHash = MinSizeT (bytesLeftToHash, dataLength);
376
377		// hash the data
378		CC_SHA256_Update (&mSHA256Context, finger, (CC_LONG)bytesToHash);
379
380		// update the pointers
381		mBytesInCurrentDigest += bytesToHash;
382		bytesLeftToHash -= bytesToHash;
383		finger += bytesToHash;
384		dataLength -= bytesToHash;
385
386		if (bytesLeftToHash == 0) // is our digest "full"?
387		{
388			FinalizeDigestAndCompare ();
389		}
390	}
391}
392
393
394
395void Download::Finalize ()
396{
397	// are there any bytes left over in the digest?
398	if (mBytesInCurrentDigest != 0)
399	{
400		FinalizeDigestAndCompare ();
401	}
402
403	if (mCurrentHash != mNumHashes) // check for underflow
404	{
405		MacOSError::throwMe (errSecureDownloadInvalidDownload);
406	}
407}
408
409