2 * Copyright (c) 2006,2011-2014 Apple Inc. All Rights Reserved.
4 * @APPLE_LICENSE_HEADER_START@
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
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.
21 * @APPLE_LICENSE_HEADER_END@
24 #include <CoreFoundation/CoreFoundation.h>
25 #include <CommonCrypto/CommonDigest.h>
27 #include <Security/Security.h>
28 #include <security_utilities/security_utilities.h>
29 #include <security_cdsa_utilities/cssmbridge.h>
30 #include <Security/cssmapplePriv.h>
32 #include "SecureDownload.h"
33 #include "SecureDownloadInternal.h"
38 static void CheckCFThingForNULL (CFTypeRef theType
)
48 Download::Download () : mDict (NULL
), mURLs (NULL
), mName (NULL
), mDate (NULL
), mHashes (NULL
), mNumHashes (0), mCurrentHash (0), mBytesInCurrentDigest (0)
54 static void ReleaseIfNotNull (CFTypeRef theThing
)
64 Download::~Download ()
66 ReleaseIfNotNull (mDict
);
71 CFArrayRef
Download::CopyURLs ()
79 CFStringRef
Download::CopyName ()
87 CFDateRef
Download::CopyDate ()
95 void Download::GoOrNoGo (SecTrustResultType result
)
99 case kSecTrustResultInvalid
:
100 case kSecTrustResultDeny
:
101 case kSecTrustResultFatalTrustFailure
:
102 case kSecTrustResultOtherError
:
103 MacOSError::throwMe (errSecureDownloadInvalidTicket
);
105 case kSecTrustResultProceed
:
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
:
115 MacOSError::throwMe (errSecureDownloadInvalidTicket
);
125 SecPolicyRef
Download::GetPolicy ()
127 SecPolicySearchRef search
;
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
)
135 MacOSError::throwMe (result
);
138 result
= SecPolicySearchCopyNext (search
, &policy
);
139 if (result
!= errSecSuccess
)
141 MacOSError::throwMe (result
);
151 #define SHA256_NAME CFSTR("SHA-256")
153 void Download::ParseTicket (CFDataRef ticket
)
155 // make a propertylist from the ticket
156 CFRef
<CFDictionaryRef
> mDict
= (CFDictionaryRef
) _SecureDownloadParseTicketXML (ticket
);
157 CheckCFThingForNULL (mDict
);
160 mURLs
= (CFArrayRef
) CFDictionaryGetValue (mDict
, SD_XML_URL
);
161 CheckCFThingForNULL (mURLs
);
163 // get the download name
164 mName
= (CFStringRef
) CFDictionaryGetValue (mDict
, SD_XML_NAME
);
165 CheckCFThingForNULL (mName
);
167 // get the download date
168 mDate
= (CFDateRef
) CFDictionaryGetValue (mDict
, SD_XML_CREATED
);
169 CheckCFThingForNULL (mDate
);
171 // get the download size
172 CFNumberRef number
= (CFNumberRef
) CFDictionaryGetValue (mDict
, SD_XML_SIZE
);
173 CFNumberGetValue (number
, kCFNumberSInt64Type
, &mDownloadSize
);
175 // get the verifications dictionary
176 CFDictionaryRef verifications
= (CFDictionaryRef
) CFDictionaryGetValue (mDict
, SD_XML_VERIFICATIONS
);
178 // from the verifications dictionary, get the hashing dictionary that we support
179 CFDictionaryRef hashInfo
= (CFDictionaryRef
) CFDictionaryGetValue (verifications
, SHA256_NAME
);
181 // from the hashing dictionary, get the sector size
182 number
= (CFNumberRef
) CFDictionaryGetValue (hashInfo
, SD_XML_SECTOR_SIZE
);
183 CFNumberGetValue (number
, kCFNumberSInt64Type
, &mSectorSize
);
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
);
191 mBytesInCurrentDigest
= 0;
196 void Download::Initialize (CFDataRef ticket
,
197 SecureDownloadTrustSetupCallback setup
,
199 SecureDownloadTrustEvaluateCallback evaluate
,
200 void* evaluateContext
)
203 SecCmsMessageRef cmsMessage
= GetCmsMessageFromData (ticket
);
206 SecPolicyRef policy
= GetPolicy ();
208 // parse the CMS message
209 int contentLevelCount
= SecCmsMessageContentLevelCount (cmsMessage
);
210 SecCmsSignedDataRef signedData
;
215 while (i
< contentLevelCount
)
217 SecCmsContentInfoRef contentInfo
= SecCmsMessageContentLevel (cmsMessage
, i
++);
218 SECOidTag contentTypeTag
= SecCmsContentInfoGetContentTypeTag (contentInfo
);
220 if (contentTypeTag
!= SEC_OID_PKCS7_SIGNED_DATA
)
225 signedData
= (SecCmsSignedDataRef
) SecCmsContentInfoGetContent (contentInfo
);
226 if (signedData
== NULL
)
228 MacOSError::throwMe (errSecureDownloadInvalidTicket
);
231 // import the certificates found in the cms message
232 result
= SecCmsSignedDataImportCerts (signedData
, NULL
, certUsageObjectSigner
, true);
235 MacOSError::throwMe (errSecureDownloadInvalidTicket
);
238 int numberOfSigners
= SecCmsSignedDataSignerInfoCount (signedData
);
241 if (numberOfSigners
== 0) // no signers? This is a possible attack
243 MacOSError::throwMe (errSecureDownloadInvalidTicket
);
246 for (j
= 0; j
< numberOfSigners
; ++j
)
248 SecTrustResultType resultType
;
250 // do basic verification of the message
251 SecTrustRef trustRef
;
252 result
= SecCmsSignedDataVerifySignerInfo (signedData
, j
, NULL
, policy
, &trustRef
);
254 // notify the user of the new trust ref
257 SecureDownloadTrustCallbackResult tcResult
= setup (trustRef
, setupContext
);
260 case kSecureDownloadDoNotEvaluateSigner
:
263 case kSecureDownloadFailEvaluation
:
264 MacOSError::throwMe (errSecureDownloadInvalidTicket
);
266 case kSecureDownloadEvaluateSigner
:
273 MacOSError::throwMe (errSecureDownloadInvalidTicket
);
276 result
= SecTrustEvaluate (trustRef
, &resultType
);
277 if (result
!= errSecSuccess
)
279 MacOSError::throwMe (errSecureDownloadInvalidTicket
);
282 if (evaluate
!= NULL
)
284 resultType
= evaluate (trustRef
, resultType
, evaluateContext
);
287 GoOrNoGo (resultType
);
291 // extract the message
292 CSSM_DATA_PTR message
= SecCmsMessageGetContent (cmsMessage
);
293 CFDataRef ticketData
= CFDataCreateWithBytesNoCopy (NULL
, message
->Data
, message
->Length
, kCFAllocatorNull
);
294 CheckCFThingForNULL (ticketData
);
296 ParseTicket (ticketData
);
299 CC_SHA256_Init (&mSHA256Context
);
302 CFRelease (ticketData
);
303 SecCmsMessageDestroy (cmsMessage
);
308 SecCmsMessageRef
Download::GetCmsMessageFromData (CFDataRef data
)
311 SecCmsDecoderRef decoderContext
;
312 int result
= SecCmsDecoderCreate (NULL
, NULL
, NULL
, NULL
, NULL
, NULL
, NULL
, &decoderContext
);
315 MacOSError::throwMe (errSecureDownloadInvalidTicket
);
318 result
= SecCmsDecoderUpdate (decoderContext
, CFDataGetBytePtr (data
), CFDataGetLength (data
));
321 SecCmsDecoderDestroy(decoderContext
);
322 MacOSError::throwMe (errSecureDownloadInvalidTicket
);
325 SecCmsMessageRef message
;
326 result
= SecCmsDecoderFinish (decoderContext
, &message
);
329 MacOSError::throwMe (errSecureDownloadInvalidTicket
);
337 size_t MinSizeT (size_t a
, size_t b
)
339 // return the smaller of a and b
340 return a
< b
? a
: b
;
345 void Download::FinalizeDigestAndCompare ()
348 CC_SHA256_Final (digest
, &mSHA256Context
);
350 // make sure we don't overflow the digest buffer
351 if (mCurrentHash
>= mNumHashes
|| memcmp (digest
, mDigests
[mCurrentHash
++], CC_SHA256_DIGEST_LENGTH
) != 0)
353 // Something's really wrong!
354 MacOSError::throwMe (errSecureDownloadInvalidDownload
);
357 // setup for the next receipt of data
358 mBytesInCurrentDigest
= 0;
359 CC_SHA256_Init (&mSHA256Context
);
364 void Download::UpdateWithData (CFDataRef data
)
366 // figure out how much data to hash
367 CFIndex dataLength
= CFDataGetLength (data
);
368 const UInt8
* finger
= CFDataGetBytePtr (data
);
370 while (dataLength
> 0)
372 // figure out how many bytes are left to hash
373 size_t bytesLeftToHash
= mSectorSize
- mBytesInCurrentDigest
;
374 size_t bytesToHash
= MinSizeT (bytesLeftToHash
, dataLength
);
377 CC_SHA256_Update (&mSHA256Context
, finger
, (CC_LONG
)bytesToHash
);
379 // update the pointers
380 mBytesInCurrentDigest
+= bytesToHash
;
381 bytesLeftToHash
-= bytesToHash
;
382 finger
+= bytesToHash
;
383 dataLength
-= bytesToHash
;
385 if (bytesLeftToHash
== 0) // is our digest "full"?
387 FinalizeDigestAndCompare ();
394 void Download::Finalize ()
396 // are there any bytes left over in the digest?
397 if (mBytesInCurrentDigest
!= 0)
399 FinalizeDigestAndCompare ();
402 if (mCurrentHash
!= mNumHashes
) // check for underflow
404 MacOSError::throwMe (errSecureDownloadInvalidDownload
);