/ SecurityTool / macOS / verify_cert.c
verify_cert.c
  1  /*
  2   * Copyright (c) 2006,2010,2012,2014-2019 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   * verify_cert.c
 24   */
 25  
 26  #include <Security/SecTrust.h>
 27  #include <Security/SecKeychain.h>
 28  #include <Security/SecPolicy.h>
 29  #include <Security/SecPolicySearch.h>
 30  #include <Security/cssmapple.h>
 31  #include <Security/oidsalg.h>
 32  #include <stdlib.h>
 33  #include <unistd.h>
 34  #include <sys/stat.h>
 35  #include <time.h>
 36  #include "trusted_cert_ssl.h"
 37  #include "trusted_cert_utils.h"
 38  #include "verify_cert.h"
 39  #include <utilities/SecCFRelease.h>
 40  #include "security_tool.h"
 41  
 42  /*
 43   * Read file as a cert, add to a CFArray, creating the array if necessary
 44   */
 45  static int addCertFile(
 46  	const char *fileName,
 47  	CFMutableArrayRef *array)
 48  {
 49  	SecCertificateRef certRef;
 50  
 51  	if(readCertFile(fileName, &certRef)) {
 52  		return -1;
 53  	}
 54  	if(*array == NULL) {
 55  		*array = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
 56  	}
 57  	CFArrayAppendValue(*array, certRef);
 58  	CFRelease(certRef);
 59  	return 0;
 60  }
 61  
 62  int
 63  verify_cert(int argc, char * const *argv)
 64  {
 65  	extern char			*optarg;
 66  	extern int			optind;
 67  	OSStatus			ortn;
 68  	int					arg;
 69  	CFMutableArrayRef	certs = NULL;
 70  	CFMutableArrayRef	roots = NULL;
 71  	CFMutableArrayRef	keychains = NULL;
 72  	CFMutableArrayRef	policies = NULL;
 73  	CFMutableDictionaryRef	properties = NULL;
 74  	const CSSM_OID		*policy = &CSSMOID_APPLE_X509_BASIC;
 75  	SecKeychainRef		kcRef = NULL;
 76  	int					ourRtn = 0;
 77  	bool				quiet = false;
 78  	bool				client = false;
 79  	bool				useTLS = false;
 80  	bool				printPem = false;
 81  	bool				printText = false;
 82  	bool				printDetails = false;
 83  	int 				verbose = 0;
 84  	SecPolicyRef		policyRef = NULL;
 85  	SecPolicyRef		revPolicyRef = NULL;
 86  	SecTrustRef			trustRef = NULL;
 87  	SecPolicySearchRef	searchRef = NULL;
 88  	CFErrorRef			errorRef = NULL;
 89  	const char			*emailAddrs = NULL;
 90  	const char			*sslHost = NULL;
 91  	const char			*name = NULL;
 92  	const char			*url = NULL;
 93  	CSSM_APPLE_TP_ACTION_FLAGS actionFlags = 0;
 94  	bool				forceActionFlags = false;
 95  	CSSM_APPLE_TP_ACTION_DATA	actionData;
 96  	CFDataRef			cfActionData = NULL;
 97  	SecTrustResultType	resultType;
 98  	OSStatus			ocrtn;
 99  	struct tm			time;
100  	CFGregorianDate		gregorianDate;
101  	CFDateRef			dateRef = NULL;
102  	CFOptionFlags		revOptions = 0;
103  
104  	if(argc < 2) {
105  		return SHOW_USAGE_MESSAGE;
106  	}
107  	/* permit network cert fetch unless explicitly turned off with '-L' */
108  	actionFlags |= CSSM_TP_ACTION_FETCH_CERT_FROM_NET;
109  	optind = 1;
110  	while ((arg = getopt(argc, argv, "Cc:r:p:k:e:s:d:LlNnPqR:tv")) != -1) {
111  		switch (arg) {
112  			case 'C':
113  				client = true;
114  				break;
115  			case 'c':
116  				/* this can be specified multiple times */
117  				if(addCertFile(optarg, &certs)) {
118  					ourRtn = 1;
119  					goto errOut;
120  				}
121  				break;
122  			case 'r':
123  				/* this can be specified multiple times */
124  				if(addCertFile(optarg, &roots)) {
125  					ourRtn = 1;
126  					goto errOut;
127  				}
128  				break;
129  			case 'p':
130  				policy = policyStringToOid(optarg, &useTLS);
131  				if(policy == NULL) {
132  					ourRtn = 2;
133  					goto errOut;
134  				}
135  				break;
136  			case 'k':
137  				ortn = SecKeychainOpen(optarg, &kcRef);
138  				if(ortn) {
139  					cssmPerror("SecKeychainOpen", ortn);
140  					ourRtn = 1;
141  					goto errOut;
142  				}
143  				/* this can be specified multiple times */
144  				if(keychains == NULL) {
145  					keychains = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
146  				}
147  				CFArrayAppendValue(keychains, kcRef);
148  				CFRelease(kcRef);
149  				break;
150  			case 'L':
151  				actionFlags &= ~CSSM_TP_ACTION_FETCH_CERT_FROM_NET;
152  				forceActionFlags = true;
153  				break;
154  			case 'l':
155  				actionFlags |= CSSM_TP_ACTION_LEAF_IS_CA;
156  				break;
157  			case 'n': {
158  				/* Legacy macOS used 'n' as the "no keychain search list" flag.
159  				   iOS interprets it as the name option, with one argument.
160  				*/
161  				char *o = argv[optind];
162  				if (o && o[0] != '-') {
163  					name = o;
164  					++optind;
165  					break;
166  				}
167  			}	/* intentional fall-through to "no keychains" case, if no arg */
168  			case 'N':
169  				/* No keychains, signalled by empty keychain array */
170  				if(keychains != NULL) {
171  					fprintf(stderr, "-k and -%c are mutually exclusive\n", arg);
172  					ourRtn = 2;
173  					goto errOut;
174  				}
175  				keychains = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
176  				break;
177  			case 'e':
178  				emailAddrs = optarg;
179  				break;
180  			case 's':
181  				sslHost = optarg;
182  				break;
183  			case 'q':
184  				quiet = true;
185  				break;
186  			case 'd':
187  				memset(&time, 0, sizeof(struct tm));
188  				if (strptime(optarg, "%Y-%m-%d-%H:%M:%S", &time) == NULL) {
189  					if (strptime(optarg, "%Y-%m-%d", &time) == NULL) {
190  						fprintf(stderr, "Date processing error\n");
191  						ourRtn = 2;
192  						goto errOut;
193  					}
194  				}
195  				gregorianDate.second = time.tm_sec;
196  				gregorianDate.minute = time.tm_min;
197  				gregorianDate.hour = time.tm_hour;
198  				gregorianDate.day = time.tm_mday;
199  				gregorianDate.month = time.tm_mon + 1;
200  				gregorianDate.year = time.tm_year + 1900;
201  
202  				if (dateRef == NULL) {
203  					dateRef = CFDateCreate(NULL, CFGregorianDateGetAbsoluteTime(gregorianDate, NULL));
204  				}
205  				break;
206  			case 'R':
207  				revOptions |= revCheckOptionStringToFlags(optarg);
208  				break;
209  			case 'P':
210  				printPem = true;
211  				break;
212  			case 't':
213  				printText = true;
214  				break;
215  			case 'v':
216  				printDetails = true;
217  				verbose++;
218  				break;
219  			default:
220  				ourRtn = 2;
221  				goto errOut;
222  		}
223  	}
224  	if(optind != argc) {
225  		if (argc > optind) {
226  			url = argv[argc-1];
227  		}
228  		if (url && *url != '\0') {
229  			useTLS = true;
230  			ourRtn = evaluate_ssl(url, verbose, &trustRef);
231  			goto post_evaluate;
232  		} else {
233  			ourRtn = 2;
234  		}
235  		goto errOut;
236  	}
237  
238  	if(certs == NULL) {
239  		if(roots == NULL) {
240  			fprintf(stderr, "***No certs specified.\n");
241  			ourRtn = 2;
242  			goto errOut;
243  		}
244  		if(CFArrayGetCount(roots) != 1) {
245  			fprintf(stderr, "***Multiple roots and no certs not allowed.\n");
246  			ourRtn = 2;
247  			goto errOut;
248  		}
249  
250  		/* no certs and one root: verify the root */
251  		certs = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
252  		CFArrayAppendValue(certs, CFArrayGetValueAtIndex(roots, 0));
253  		actionFlags |= CSSM_TP_ACTION_LEAF_IS_CA;
254  	}
255  
256  	/* cook up a SecPolicyRef */
257  	properties = CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
258  			&kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
259  	if (!properties) {
260  		cssmPerror("CFDictionaryCreateMutable", errSecMemoryError);
261  		ourRtn = 1;
262  		goto errOut;
263  	} else {
264  		/* if a policy name was specified to match, set it in the dictionary */
265  		const char *nameStr = name;
266  		if (!nameStr) { nameStr = (sslHost) ? sslHost : ((emailAddrs) ? emailAddrs : NULL); }
267  		CFStringRef nameRef = (nameStr) ?  CFStringCreateWithBytes(NULL,
268  			(const UInt8 *)nameStr, (CFIndex)strlen(nameStr), kCFStringEncodingUTF8, false) : NULL;
269  		if (nameRef) {
270  			CFDictionarySetValue(properties, kSecPolicyName, nameRef);
271  			CFRELEASE(nameRef);
272  		}
273  		CFStringRef policyID = NULL;
274  		if (compareOids(policy, &CSSMOID_APPLE_TP_SSL)) {
275  			policyID = kSecPolicyAppleSSL;
276  		} else if (compareOids(policy, &CSSMOID_APPLE_TP_EAP)) {
277  			policyID = kSecPolicyAppleEAP;
278  		} else if (compareOids(policy, &CSSMOID_APPLE_TP_APPLEID_SHARING)) {
279  			policyID = kSecPolicyAppleIDValidation;
280  		} else if (compareOids(policy, &CSSMOID_APPLE_TP_SMIME)) {
281  			policyID = kSecPolicyAppleSMIME;
282  		}
283  		if (policyID) {
284  			policyRef = SecPolicyCreateWithProperties(policyID, properties);
285  		}
286  	}
287  	if (!policyRef) {
288  		/* all other policies not handled above */
289  		ortn = SecPolicySearchCreate(CSSM_CERT_X_509v3,
290  			policy,
291  			NULL,				// policy opts
292  			&searchRef);
293  		if(ortn) {
294  			cssmPerror("SecPolicySearchCreate", ortn);
295  			ourRtn = 1;
296  			goto errOut;
297  		}
298  		ortn = SecPolicySearchCopyNext(searchRef, &policyRef);
299  		if(ortn) {
300  			cssmPerror("SecPolicySearchCopyNext", ortn);
301  			ourRtn = 1;
302  			goto errOut;
303  		}
304  	}
305  
306  	/* create policies array */
307  	policies = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
308  	CFArrayAppendValue(policies, policyRef);
309  	/* add optional SecPolicyRef for revocation, if specified */
310  	if(revOptions != 0) {
311  		revPolicyRef = SecPolicyCreateRevocation(revOptions);
312  		CFArrayAppendValue(policies, revPolicyRef);
313  	}
314  
315  	/* create trust reference from certs and policies */
316  	ortn = SecTrustCreateWithCertificates(certs, policies, &trustRef);
317  	if(ortn) {
318  		cssmPerror("SecTrustCreateWithCertificates", ortn);
319  		ourRtn = 1;
320  		goto errOut;
321  	}
322  
323  	/* roots (anchors) are optional */
324  	if(roots != NULL) {
325  		ortn = SecTrustSetAnchorCertificates(trustRef, roots);
326  		if(ortn) {
327  			cssmPerror("SecTrustSetAnchorCertificates", ortn);
328  			ourRtn = 1;
329  			goto errOut;
330  		}
331  	}
332  	if(actionFlags || forceActionFlags) {
333  		memset(&actionData, 0, sizeof(actionData));
334  		actionData.Version = CSSM_APPLE_TP_ACTION_VERSION;
335  		actionData.ActionFlags = actionFlags;
336  		cfActionData = CFDataCreate(NULL, (UInt8 *)&actionData, sizeof(actionData));
337  		ortn = SecTrustSetParameters(trustRef, CSSM_TP_ACTION_DEFAULT, cfActionData);
338  		if(ortn) {
339  			cssmPerror("SecTrustSetParameters", ortn);
340  			ourRtn = 1;
341  			goto errOut;
342  		}
343  	}
344  	if(keychains) {
345  		ortn = SecTrustSetKeychains(trustRef, keychains);
346  		if(ortn) {
347  			cssmPerror("SecTrustSetKeychains", ortn);
348  			ourRtn = 1;
349  			goto errOut;
350  		}
351  	}
352  	if(dateRef != NULL) {
353  		ortn = SecTrustSetVerifyDate(trustRef, dateRef);
354  		if(ortn) {
355  			cssmPerror("SecTrustSetVerifyDate", ortn);
356  			ourRtn = 1;
357  			goto errOut;
358  		}
359  	}
360  
361  	/* GO */
362  	(void)SecTrustEvaluateWithError(trustRef, &errorRef);
363  post_evaluate:
364  	ortn = SecTrustGetTrustResult(trustRef, &resultType);
365  	if(ortn) {
366  		/* should never fail - error on this doesn't mean the cert verified badly */
367  		cssmPerror("SecTrustEvaluate", ortn);
368  		ourRtn = 1;
369  		goto errOut;
370  	}
371  	switch(resultType) {
372  		case kSecTrustResultUnspecified:
373  			/* cert chain valid, no special UserTrust assignments */
374  		case kSecTrustResultProceed:
375  			/* cert chain valid AND user explicitly trusts this */
376  			break;
377  		case kSecTrustResultDeny:
378  			if(!quiet) {
379  				fprintf(stderr, "SecTrustEvaluate result: kSecTrustResultDeny\n");
380  			}
381  			ourRtn = 1;
382  			break;
383  		default:
384  			ourRtn = 1;
385  			if(!quiet) {
386  				/* See what the TP had to say about this */
387  				ortn = SecTrustGetCssmResultCode(trustRef, &ocrtn);
388  				if(ortn) {
389  					cssmPerror("SecTrustGetCssmResultCode", ortn);
390  				}
391  				else  {
392  					cssmPerror("Cert Verify Result", ocrtn);
393  				}
394  			}
395  			break;
396  	}
397  	if((ourRtn == 0) & !quiet) {
398  		printf("...certificate verification successful.\n");
399  	}
400  	if (printPem || printText || verbose) {
401  		fprintf(stdout, "---\nCertificate chain\n");
402  		printCertChain(trustRef, printPem, printText);
403  	}
404  	if (verbose) {
405  		printErrorDetails(trustRef);
406  	}
407  	if (useTLS) {
408  		printExtendedResults(trustRef);
409  	}
410  	if (printDetails) {
411  		CFArrayRef properties = SecTrustCopyProperties(trustRef);
412  		if (verbose > 1) {
413  			fprintf(stderr, "---\nCertificate chain properties\n");
414  			CFShow(properties); // output goes to stderr
415  		}
416  		if (properties) {
417  			CFRelease(properties);
418  		}
419  		CFDictionaryRef result = SecTrustCopyResult(trustRef);
420  		if (result) {
421  			fprintf(stderr, "---\nTrust evaluation results\n");
422  			CFShow(result); // output goes to stderr
423  			CFRelease(result);
424  		}
425  		if (errorRef) {
426  			fprintf(stdout, "---\nTrust evaluation errors\n");
427  			CFShow(errorRef);
428  		}
429  	}
430  
431  errOut:
432  	/* cleanup */
433  	CFRELEASE(certs);
434  	CFRELEASE(roots);
435  	CFRELEASE(keychains);
436  	CFRELEASE(properties);
437  	CFRELEASE(policies);
438  	CFRELEASE(revPolicyRef);
439  	CFRELEASE(dateRef);
440  	CFRELEASE(policyRef);
441  	CFRELEASE(trustRef);
442  	CFRELEASE(searchRef);
443  	CFRELEASE(errorRef);
444  	CFRELEASE(cfActionData);
445  	return ourRtn;
446  }