]>
Commit | Line | Data |
---|---|---|
b1ab9ed8 | 1 | /* |
d8f41ccd | 2 | * Copyright (c) 2006,2011-2014 Apple Inc. All Rights Reserved. |
b1ab9ed8 A |
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> | |
b1ab9ed8 A |
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 kSecTrustResultConfirm: | |
113 | case kSecTrustResultRecoverableTrustFailure: | |
114 | case kSecTrustResultUnspecified: | |
115 | { | |
116 | MacOSError::throwMe (errSecureDownloadInvalidTicket); | |
117 | } | |
118 | ||
119 | default: | |
120 | break; | |
121 | } | |
122 | } | |
123 | ||
124 | ||
125 | ||
126 | SecPolicyRef 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); | |
427c49bc | 134 | if (result != errSecSuccess) |
b1ab9ed8 A |
135 | { |
136 | MacOSError::throwMe (result); | |
137 | } | |
138 | ||
139 | result = SecPolicySearchCopyNext (search, &policy); | |
427c49bc | 140 | if (result != errSecSuccess) |
b1ab9ed8 A |
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 | ||
154 | void 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 | ||
197 | void 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); | |
427c49bc | 278 | if (result != errSecSuccess) |
b1ab9ed8 A |
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 | ||
309 | SecCmsMessageRef 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 | ||
427c49bc | 337 | static |
b1ab9ed8 A |
338 | size_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 | ||
346 | void 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 | ||
365 | void 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 | |
427c49bc | 378 | CC_SHA256_Update (&mSHA256Context, finger, (CC_LONG)bytesToHash); |
b1ab9ed8 A |
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 | ||
395 | void 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 |