1//
2//  ValidateAsset.c
3//  CertificateTool
4//
5//  Copyright (c) 2012-2013 Apple Inc. All Rights Reserved.
6//
7
8#include <CoreFoundation/CoreFoundation.h>
9#include <Security/Security.h>
10
11#include <CommonCrypto/CommonDigest.h>
12#include <CommonCrypto/CommonDigestSPI.h>
13#include <CommonCrypto/CommonRSACryptor.h>
14#include <CommonNumerics/CommonBaseXX.h>
15#include <stdio.h>
16#include <sys/types.h>
17#include <dirent.h>
18#include <string.h>
19#include <unistd.h>
20
21
22static int ValidateFilesInDirectory(const char* dir_path, int num_files, const char * files[])
23{
24
25	int result = 0; 	// Assume all is well
26	DIR* dirp = NULL;
27	struct dirent* dp = NULL;
28
29	dirp = opendir(dir_path);
30	if (NULL == dirp)
31	{
32		return -1;
33	}
34
35	for (int iCnt = 0; iCnt < num_files; iCnt++)
36	{
37		int name_length = (int)strlen(files[iCnt]);
38		int found = 0;
39		while (NULL != (dp = readdir(dirp)))
40		{
41			if (dp->d_namlen == name_length && 0 == strcmp(dp->d_name, files[iCnt]))
42			{
43				found = 1;
44                break;
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 != aPlistRef)
169        {
170            CFRelease(aPlistRef);
171        }
172
173		if (NULL != error)
174		{
175			CFRelease(error);
176		}
177		return result;
178	}
179
180	if (CFGetTypeID(aPlistRef) != output_type)
181	{
182		CFRelease(aPlistRef);
183		return result;
184	}
185
186	*plistRef = aPlistRef;
187    result = (NULL == *plistRef) ? -1 : 0;
188	return result;
189}
190
191
192
193int ValidateAsset(const char* asset_dir_path, unsigned long current_version)
194{
195	const char* files[] =
196	{
197        "AssertVersion.plist",
198		"Blocked.plist",
199		"EVRoots.plist",
200		"certsIndex.data",
201		"certsTable.data",
202		"manifest.data"
203	};
204	int num_files = (sizeof(files) / sizeof(const char*));
205	int iCnt = 0;
206	const char* file_name = NULL;
207    char wd_buf[1024];
208
209	const char* current_working_directory_path = getcwd(wd_buf, 1024);
210	CFDataRef file_data = NULL;
211	CFDataRef hash_data = NULL;
212	CFDataRef manifest_hash_data = NULL;
213	CFStringRef key_name = NULL;
214	CFDictionaryRef manifest_dict = NULL;
215    CFNumberRef manifest_version = NULL;
216    unsigned long manifest_verson_number;
217	int iResult = -1;
218	OSStatus err = errSecSuccess;
219	CMSDecoderRef cmsDecoder = NULL;
220	size_t numSigners = 0;
221	SecPolicyRef x509Policy = NULL;
222	CMSSignerStatus signedStatus = kCMSSignerUnsigned;
223	OSStatus resultCode = errSecSuccess;
224	CFDataRef cmsMsg = NULL;
225
226	// parameter check
227	if (NULL == asset_dir_path)
228	{
229		return iResult;
230	}
231
232	if (ValidateFilesInDirectory(asset_dir_path, num_files, files))
233	{
234		return iResult;
235	}
236
237	if (chdir(asset_dir_path))
238	{
239		return iResult;
240	}
241
242	if (ReadFileIntoCFDataRef("manifest.data", &file_data))
243	{
244		(void)chdir(current_working_directory_path);
245		return iResult;
246	}
247
248    if (NULL == file_data)
249    {
250        return iResult;
251    }
252
253	err = CMSDecoderCreate(&cmsDecoder);
254	if (errSecSuccess != err)
255	{
256		CFRelease(file_data);
257		(void)chdir(current_working_directory_path);
258		return iResult;
259	}
260
261	err = CMSDecoderUpdateMessage(cmsDecoder, CFDataGetBytePtr(file_data), CFDataGetLength(file_data));
262	CFRelease(file_data);
263	if (errSecSuccess != err)
264	{
265		CFRelease(cmsDecoder);
266		(void)chdir(current_working_directory_path);
267		return iResult;
268	}
269
270	err = CMSDecoderFinalizeMessage(cmsDecoder);
271	if (errSecSuccess != err)
272	{
273		CFRelease(cmsDecoder);
274		(void)chdir(current_working_directory_path);
275		return iResult;
276	}
277
278	err = CMSDecoderGetNumSigners(cmsDecoder, &numSigners);
279	if (errSecSuccess != err || 0 == numSigners)
280	{
281		CFRelease(cmsDecoder);
282		(void)chdir(current_working_directory_path);
283		return iResult;
284	}
285
286	x509Policy = SecPolicyCreateBasicX509();
287	if (NULL == x509Policy)
288	{
289		CFRelease(cmsDecoder);
290		(void)chdir(current_working_directory_path);
291		return iResult;
292	}
293
294	err = CMSDecoderCopySignerStatus(cmsDecoder, 0, x509Policy, true, &signedStatus, NULL, &resultCode);
295	CFRelease(x509Policy);
296	if (errSecSuccess != err || kCMSSignerValid != signedStatus || errSecSuccess != resultCode)
297	{
298		CFRelease(cmsDecoder);
299		(void)chdir(current_working_directory_path);
300		return iResult;
301	}
302
303	err = CMSDecoderCopyContent(cmsDecoder, &cmsMsg);
304	CFRelease(cmsDecoder);
305	if (errSecSuccess != err)
306	{
307		(void)chdir(current_working_directory_path);
308		return iResult;
309	}
310
311	if (CreatePropertyListFromData(cmsMsg,  CFDictionaryGetTypeID(), (CFTypeRef *)&manifest_dict))
312	{
313        if (NULL != manifest_dict)
314        {
315            CFRelease(manifest_dict);
316        }
317		CFRelease(cmsMsg);
318		(void)chdir(current_working_directory_path);
319		return iResult;
320	}
321	CFRelease(cmsMsg);
322
323	// Validate the hash for the files in the manifest
324	for (iCnt = 0; iCnt < num_files; iCnt++)
325	{
326		file_name = files[iCnt];
327		// bypass the manifest file for now
328		if (!strcmp("manifest.data", file_name))
329		{
330			continue;
331		}
332
333		if (ReadFileIntoCFDataRef(file_name, &file_data))
334		{
335			CFRelease(manifest_dict);
336			(void)chdir(current_working_directory_path);
337			return iResult;
338		}
339
340		if (CreateHashForData(file_data, 0, &hash_data))
341		{
342            if (NULL != file_data)
343            {
344                CFRelease(file_data);
345            }
346			CFRelease(manifest_dict);
347			(void)chdir(current_working_directory_path);
348			return iResult;
349		}
350		CFRelease(file_data);
351
352		key_name = CFStringCreateWithCString(kCFAllocatorDefault, file_name, kCFStringEncodingUTF8);
353		if (NULL == key_name)
354		{
355			CFRelease(manifest_dict);
356			(void)chdir(current_working_directory_path);
357			return iResult;
358		}
359
360		manifest_hash_data = (CFDataRef)CFDictionaryGetValue(manifest_dict, key_name);
361		if (NULL == manifest_hash_data)
362		{
363			CFRelease(key_name);
364			CFRelease(hash_data);
365			CFRelease(manifest_dict);
366			(void)chdir(current_working_directory_path);
367			return iResult;
368		}
369		CFRelease(key_name);
370
371		if (!CFEqual(hash_data, manifest_hash_data))
372		{
373			CFRelease(hash_data);
374			CFRelease(manifest_dict);
375			(void)chdir(current_working_directory_path);
376			return iResult;
377		}
378		CFRelease(hash_data);
379	}
380
381
382	// Get the version
383    manifest_version = (CFNumberRef)CFDictionaryGetValue(manifest_dict, CFSTR("VersionNumber"));
384    if (NULL == manifest_version)
385    {
386        CFRelease(manifest_dict);
387		(void)chdir(current_working_directory_path);
388		return iResult;
389    }
390
391    if (!CFNumberGetValue(manifest_version, kCFNumberLongType, &manifest_verson_number))
392    {
393        CFRelease(manifest_version);
394        CFRelease(manifest_dict);
395		(void)chdir(current_working_directory_path);
396		return iResult;
397    }
398
399    CFRelease(manifest_version);
400    if (manifest_verson_number < current_version)
401    {
402        CFRelease(manifest_dict);
403		(void)chdir(current_working_directory_path);
404		return iResult;
405    }
406
407    iResult = 0;
408	return iResult;
409}
410