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}