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