1// 2// ValidateAsset.c 3// ios_ota_cert_tool 4// 5// Created by James Murphy on 12/13/12. 6// Copyright (c) 2012 James Murphy. All rights reserved. 7// 8 9#include <CoreFoundation/CoreFoundation.h> 10#include <CommonCrypto/CommonDigest.h> 11#include <CommonCrypto/CommonDigestSPI.h> 12#include <CommonCrypto/CommonRSACryptor.h> 13#include <CommonNumerics/CommonBaseXX.h> 14#include <stdio.h> 15#include <sys/types.h> 16#include <dirent.h> 17#include <string.h> 18#include <unistd.h> 19 20 21static const char* kPublicManifestKeyData = "MIIBCgKCAQEA7eev+hip+8Vg1kj/q4qnpN37X8vaKZouAyoXZ6gy+2D2wKxR0KORuV9bCFkcyT+LST/Rhn+64YNSZ7UvkQRlU34vZcF7FWuPfEbLGcCG7e1hlshHVUUah+07Qyu82f6OAg8PBFvYwvZHZMcXlvZJQjdNtbIORQfdlrGpRN1C6xKfbX6IKE9LViGQJmljdRuaK/SxmKyMsLfsTCzh+6yxMpPtY75PuSfrVcDSlGhr108QfP5n2WQ9frtyFgdowlXr/kECWSUrj8qDk1JymVd2ZyF3dlTWdzSO17vDt6caQyjQmTyGrGRGOM6THSq9mB/fv1Q5gxEfIIb8SejTvu4GSwIDAQAB"; 22 23static int ValidateFilesInDirectory(const char* dir_path, int num_files, const char * files[]) 24{ 25 26 int result = 0; // Assume all is well 27 DIR* dirp = NULL; 28 struct dirent* dp = NULL; 29 30 dirp = opendir(dir_path); 31 if (NULL == dirp) 32 { 33 return -1; 34 } 35 36 for (int iCnt = 0; iCnt < num_files; iCnt++) 37 { 38 int name_length = (int)strlen(files[iCnt]); 39 int found = 0; 40 while (NULL != (dp = readdir(dirp))) 41 { 42 if (dp->d_namlen == name_length && 0 == strcmp(dp->d_name, files[iCnt])) 43 { 44 found = 1; 45 } 46 } 47 if (0 == found) 48 { 49 (void)closedir(dirp); 50 51 return -1; 52 } 53 rewinddir(dirp); 54 } 55 (void)closedir(dirp); 56 return result; 57} 58 59static int ReadFileIntoCFDataRef(const char* file_path, CFDataRef* out_data) 60{ 61 int result = -1; // guilt until proven 62 FILE* infile = NULL; 63 void* buffer = NULL; 64 int numbytes = 0; 65 66 if (NULL == file_path || NULL == out_data) 67 { 68 return result; 69 } 70 71 infile = fopen(file_path, "r"); 72 if (NULL == infile) 73 { 74 return result; 75 } 76 77 fseek(infile, 0L, SEEK_END); 78 numbytes = (int)ftell(infile); 79 80 fseek(infile, 0L, SEEK_SET); 81 buffer = calloc(numbytes, sizeof(char)); 82 if (NULL == buffer) 83 { 84 fclose(infile); 85 return result; 86 } 87 88 fread(buffer, sizeof(char), numbytes, infile); 89 fclose(infile); 90 91 *out_data = CFDataCreate(kCFAllocatorDefault, (const UInt8 *)buffer, numbytes); 92 free(buffer); 93 result = (NULL != *out_data) ? 0 : -1; 94 return result; 95} 96 97static int CreateHashForData(CFDataRef cfData, int useSHA1, CFDataRef* out_hash) 98{ 99 int result = -1; // Guilty until proven 100 CCDigestAlgorithm algo = (useSHA1) ? kCCDigestSHA1 :kCCDigestSHA256; 101 size_t digest_length = (useSHA1) ? CC_SHA1_DIGEST_LENGTH : CC_SHA256_DIGEST_LENGTH; 102 UInt8 buffer[digest_length]; 103 104 if (NULL == cfData || NULL == out_hash) 105 { 106 return result; 107 } 108 109 *out_hash = NULL; 110 111 memset(buffer, 0, digest_length); 112 113 if (!CCDigest(algo, CFDataGetBytePtr(cfData), CFDataGetLength(cfData), buffer)) 114 { 115 *out_hash = CFDataCreate(kCFAllocatorDefault, (const UInt8 *)buffer, digest_length); 116 } 117 else 118 { 119 return result; 120 } 121 122 result = (NULL == *out_hash) ? -1 : 0; 123 return result; 124} 125 126static int Base64Data(CFDataRef cfData, int for_encoding, CFDataRef* encoded_data) 127{ 128 int result = -1; // Guilty until proven 129 CNEncodings encoding = kCNEncodingBase64; 130 CNStatus status = kCCSuccess; 131 CNEncodingDirection direction = (for_encoding) ? kCNEncode : kCNDecode; 132 unsigned char buffer[1024]; 133 size_t encoded_data_length = 1024; 134 135 if (NULL == cfData || NULL == encoded_data) 136 { 137 return result; 138 } 139 memset(buffer, 0, 1024); 140 *encoded_data = NULL; 141 142 status = CNEncode(encoding, direction, CFDataGetBytePtr(cfData), CFDataGetLength(cfData), buffer, &encoded_data_length); 143 if (kCCSuccess == status) 144 { 145 *encoded_data = CFDataCreate(kCFAllocatorDefault, (const UInt8 *)buffer, encoded_data_length); 146 result = (NULL == *encoded_data) ? -1 : 0; 147 } 148 return result; 149} 150 151static int CreatePropertyListFromData(CFDataRef prop_data, CFTypeID output_type, CFTypeRef* plistRef) 152{ 153 int result = -1; // Guilt until proven 154 CFPropertyListRef aPlistRef = NULL; 155 CFPropertyListFormat list_format = kCFPropertyListXMLFormat_v1_0; 156 CFErrorRef error = NULL; 157 158 if (NULL == prop_data || NULL == plistRef) 159 { 160 return result; 161 } 162 163 *plistRef = NULL; 164 165 aPlistRef = CFPropertyListCreateWithData(kCFAllocatorDefault, prop_data, 0, &list_format, &error); 166 if (NULL != error || NULL == aPlistRef) 167 { 168 if (NULL != error) 169 { 170 CFRelease(error); 171 } 172 return result; 173 } 174 175 if (CFGetTypeID(aPlistRef) != output_type) 176 { 177 CFRelease(aPlistRef); 178 return result; 179 } 180 181 *plistRef = aPlistRef; 182 result = (NULL == *plistRef) ? -1 : 0; 183 return result; 184} 185 186 187static int TearOffSignatureAndHashManifest(CFDictionaryRef manifestDict, CFDataRef* signature, CFDataRef* manifest_data) 188{ 189 int result = -1; 190 CFMutableDictionaryRef new_manifest_dict = NULL; 191 CFStringRef sig_data_str = NULL; 192 CFDataRef sig_data = NULL; 193 CFDataRef prop_list = NULL; 194 CFDataRef decoded_sig_data = NULL; 195 196 if (NULL == manifestDict || NULL == signature || NULL == manifest_data) 197 { 198 return result; 199 } 200 *signature = NULL; 201 *manifest_data = NULL; 202 203 new_manifest_dict = CFDictionaryCreateMutableCopy(kCFAllocatorDefault, CFDictionaryGetCount(manifestDict), manifestDict); 204 sig_data_str = (CFStringRef)CFDictionaryGetValue(new_manifest_dict, CFSTR("Signature")); 205 if (NULL == sig_data_str) 206 { 207 CFRelease(new_manifest_dict); 208 return result; 209 } 210 211 sig_data = CFStringCreateExternalRepresentation(kCFAllocatorDefault, sig_data_str, kCFStringEncodingUTF8, 0); 212 if (NULL == sig_data) 213 { 214 CFRelease(sig_data_str); 215 CFRelease(new_manifest_dict); 216 return result; 217 } 218 219 if (Base64Data(sig_data, 0, &decoded_sig_data)) 220 { 221 CFRelease(sig_data); 222 CFRelease(new_manifest_dict); 223 return result; 224 } 225 226 *signature = decoded_sig_data; 227 228 CFDictionaryRemoveValue(new_manifest_dict, CFSTR("Signature")); 229 prop_list = CFPropertyListCreateXMLData (kCFAllocatorDefault, new_manifest_dict); 230 CFRelease(new_manifest_dict); 231 if (NULL == prop_list) 232 { 233 return result; 234 } 235 236 (void)CreateHashForData(prop_list, 1, manifest_data); 237 238 result = (NULL == *manifest_data) ? -1 : 0; 239 return result; 240} 241 242static int GetPublicManifestKey(CCRSACryptorRef* key_ref) 243{ 244 int result = -1; 245 246 CFStringRef encoded_key_data_str = NULL; 247 CFDataRef encoded_key_data_str_data = NULL; 248 CFDataRef decoded_key_data = NULL; 249 CCCryptorStatus ccStatus = kCCSuccess; 250 251 if (NULL == key_ref) 252 { 253 return result; 254 } 255 *key_ref = NULL; 256 257 encoded_key_data_str = CFStringCreateWithCString(kCFAllocatorDefault, kPublicManifestKeyData, kCFStringEncodingUTF8); 258 if (NULL == encoded_key_data_str) 259 { 260 return result; 261 } 262 263 encoded_key_data_str_data = CFStringCreateExternalRepresentation(kCFAllocatorDefault, encoded_key_data_str, kCFStringEncodingUTF8, 0); 264 if (NULL == encoded_key_data_str_data) 265 { 266 CFRelease(encoded_key_data_str); 267 return result; 268 } 269 CFRelease(encoded_key_data_str); 270 271 if (Base64Data(encoded_key_data_str_data, 0, &decoded_key_data)) 272 { 273 CFRelease(encoded_key_data_str_data); 274 return result; 275 } 276 CFRelease(encoded_key_data_str_data); 277 278 ccStatus = CCRSACryptorImport(CFDataGetBytePtr(decoded_key_data), CFDataGetLength(decoded_key_data), key_ref); 279 CFRelease(decoded_key_data); 280 281 if (kCCSuccess != ccStatus) 282 { 283 *key_ref = NULL; 284 } 285 else 286 { 287 result = 0; 288 } 289 return result; 290} 291 292 293static int ValidateSignature(CFDataRef signature, CFDataRef data) 294{ 295 int result = -1; 296 CCRSACryptorRef key_ref = NULL; 297 CCCryptorStatus ccStatus = kCCSuccess; 298 299 300 if (NULL == signature || NULL == data) 301 { 302 return result; 303 } 304 305 // Get the key 306 if (GetPublicManifestKey(&key_ref)) 307 { 308 return result; 309 } 310 311 const void *hash_data_ptr = CFDataGetBytePtr(data); 312 size_t hash_data_len = CFDataGetLength(data); 313 314 const void* sig_data_pre = CFDataGetBytePtr(signature); 315 size_t sig_dat_len = CFDataGetLength(signature); 316 317 ccStatus = CCRSACryptorVerify( 318 key_ref, 319 ccPKCS1Padding, 320 hash_data_ptr, 321 hash_data_len, 322 kCCDigestSHA1, 323 0, 324 sig_data_pre, 325 sig_dat_len); 326 327 328 CCRSACryptorRelease(key_ref); 329 330 result = (kCCSuccess == ccStatus) ? 0 : -1; 331 return result; 332} 333 334int ValidateAsset(const char* asset_dir_path, unsigned long current_version) 335{ 336 const char* files[] = 337 { 338 "certs.plist", 339 "distrusted.plist", 340 "EVRoots.plist", 341 "Manifest.plist", 342 "revoked.plist", 343 "roots.plist" 344 }; 345 int num_files = (sizeof(files) / sizeof(const char*)); 346 int iCnt = 0; 347 const char* file_name = NULL; 348 char wd_buf[1024]; 349 350 const char* current_working_directory_path = getcwd(wd_buf, 1024); 351 CFDataRef file_data = NULL; 352 CFDataRef hash_data = NULL; 353 CFDataRef encoded_hash_data = NULL; 354 CFStringRef manifest_hash_data_str = NULL; 355 CFDataRef signature_data = NULL; 356 CFStringRef key_name = NULL; 357 CFDictionaryRef manifest_dict = NULL; 358 CFNumberRef manifest_version = NULL; 359 CFStringRef encoded_hash_str = NULL; 360 CFDataRef manifest_data = NULL; 361 unsigned long manifest_verson_number; 362 int iResult = -1; 363 364 // parameter check 365 if (NULL == asset_dir_path) 366 { 367 return iResult; 368 } 369 370 if (ValidateFilesInDirectory(asset_dir_path, num_files, files)) 371 { 372 return iResult; 373 } 374 375 if (chdir(asset_dir_path)) 376 { 377 return iResult; 378 } 379 380 if (ReadFileIntoCFDataRef("Manifest.plist", &file_data)) 381 { 382 (void)chdir(current_working_directory_path); 383 return iResult; 384 } 385 386 if (CreatePropertyListFromData(file_data, CFDictionaryGetTypeID(), (CFTypeRef *)&manifest_dict)) 387 { 388 CFRelease(file_data); 389 (void)chdir(current_working_directory_path); 390 return iResult; 391 } 392 CFRelease(file_data); 393 394 // Validate the hash for the files in the manifest 395 for (iCnt = 0; iCnt < num_files; iCnt++) 396 { 397 file_name = files[iCnt]; 398 // bypass the manifest file for now 399 if (!strcmp("Manifest.plist", file_name)) 400 { 401 continue; 402 } 403 404 if (ReadFileIntoCFDataRef(file_name, &file_data)) 405 { 406 CFRelease(manifest_dict); 407 (void)chdir(current_working_directory_path); 408 return iResult; 409 } 410 411 if (CreateHashForData(file_data, 0, &hash_data)) 412 { 413 CFRelease(file_data); 414 CFRelease(manifest_dict); 415 (void)chdir(current_working_directory_path); 416 return iResult; 417 } 418 CFRelease(file_data); 419 420 421 if (Base64Data(hash_data, 1, &encoded_hash_data)) 422 { 423 CFRelease(hash_data); 424 CFRelease(manifest_dict); 425 (void)chdir(current_working_directory_path); 426 return iResult; 427 } 428 CFRelease(hash_data); 429 430 encoded_hash_str = CFStringCreateFromExternalRepresentation(kCFAllocatorDefault, encoded_hash_data, kCFStringEncodingUTF8); 431 if (NULL == encoded_hash_str) 432 { 433 CFRelease(encoded_hash_data); 434 CFRelease(manifest_dict); 435 (void)chdir(current_working_directory_path); 436 return iResult; 437 } 438 CFRelease(encoded_hash_data); 439 440 key_name = CFStringCreateWithCString(kCFAllocatorDefault, file_name, kCFStringEncodingUTF8); 441 if (NULL == key_name) 442 { 443 CFRelease(encoded_hash_str); 444 CFRelease(manifest_dict); 445 (void)chdir(current_working_directory_path); 446 return iResult; 447 } 448 449 manifest_hash_data_str = (CFStringRef)CFDictionaryGetValue(manifest_dict, key_name); 450 if (NULL == manifest_hash_data_str) 451 { 452 CFRelease(key_name); 453 CFRelease(encoded_hash_str); 454 CFRelease(manifest_dict); 455 (void)chdir(current_working_directory_path); 456 return iResult; 457 } 458 CFRelease(key_name); 459 460 if (!CFEqual(encoded_hash_str, manifest_hash_data_str)) 461 { 462 CFRelease(encoded_hash_str); 463 CFRelease(manifest_dict); 464 (void)chdir(current_working_directory_path); 465 return iResult; 466 } 467 CFRelease(encoded_hash_str); 468 } 469 470 // Get the version 471 manifest_version = (CFNumberRef)CFDictionaryGetValue(manifest_dict, CFSTR("Version")); 472 if (NULL == manifest_version) 473 { 474 CFRelease(manifest_dict); 475 (void)chdir(current_working_directory_path); 476 return iResult; 477 } 478 479 if (!CFNumberGetValue(manifest_version, kCFNumberLongType, &manifest_verson_number)) 480 { 481 CFRelease(manifest_version); 482 CFRelease(manifest_dict); 483 (void)chdir(current_working_directory_path); 484 return iResult; 485 } 486 CFRelease(manifest_version); 487 if (manifest_verson_number < current_version) 488 { 489 CFRelease(manifest_dict); 490 (void)chdir(current_working_directory_path); 491 return iResult; 492 } 493 494 // Deal with the signature 495 if (TearOffSignatureAndHashManifest(manifest_dict, &signature_data, &manifest_data)) 496 { 497 CFRelease(manifest_dict); 498 (void)chdir(current_working_directory_path); 499 return iResult; 500 } 501 502 iResult = ValidateSignature(signature_data, manifest_data); 503 CFRelease(signature_data); 504 CFRelease(manifest_data); 505 CFRelease(manifest_dict); 506 (void)chdir(current_working_directory_path); 507 return iResult; 508}