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