]>
Commit | Line | Data |
---|---|---|
d8f41ccd | 1 | /* |
866f8763 | 2 | * Copyright (c) 2006,2010,2012,2014-2017 Apple Inc. All Rights Reserved. |
d8f41ccd A |
3 | * |
4 | * @APPLE_LICENSE_HEADER_START@ | |
866f8763 | 5 | * |
d8f41ccd A |
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. | |
866f8763 | 12 | * |
d8f41ccd A |
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. | |
866f8763 | 20 | * |
d8f41ccd A |
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> | |
822b670c A |
34 | #include <sys/stat.h> |
35 | #include <time.h> | |
d8f41ccd | 36 | #include "trusted_cert_utils.h" |
fa7225c8 | 37 | #include "verify_cert.h" |
866f8763 | 38 | #include <utilities/SecCFRelease.h> |
d8f41ccd A |
39 | |
40 | /* | |
41 | * Read file as a cert, add to a CFArray, creating the array if necessary | |
42 | */ | |
43 | static int addCertFile( | |
44 | const char *fileName, | |
45 | CFMutableArrayRef *array) | |
46 | { | |
47 | SecCertificateRef certRef; | |
48 | ||
49 | if(readCertFile(fileName, &certRef)) { | |
50 | return -1; | |
51 | } | |
52 | if(*array == NULL) { | |
53 | *array = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); | |
54 | } | |
55 | CFArrayAppendValue(*array, certRef); | |
56 | CFRelease(certRef); | |
57 | return 0; | |
58 | } | |
59 | ||
60 | int | |
61 | verify_cert(int argc, char * const *argv) | |
62 | { | |
63 | extern char *optarg; | |
64 | extern int optind; | |
65 | OSStatus ortn; | |
66 | int arg; | |
67 | CFMutableArrayRef certs = NULL; | |
68 | CFMutableArrayRef roots = NULL; | |
69 | CFMutableArrayRef keychains = NULL; | |
866f8763 | 70 | CFMutableArrayRef policies = NULL; |
d8f41ccd A |
71 | const CSSM_OID *policy = &CSSMOID_APPLE_X509_BASIC; |
72 | SecKeychainRef kcRef = NULL; | |
73 | int ourRtn = 0; | |
74 | bool quiet = false; | |
866f8763 | 75 | bool client = false; |
d8f41ccd | 76 | SecPolicyRef policyRef = NULL; |
866f8763 | 77 | SecPolicyRef revPolicyRef = NULL; |
d8f41ccd A |
78 | SecTrustRef trustRef = NULL; |
79 | SecPolicySearchRef searchRef = NULL; | |
80 | const char *emailAddrs = NULL; | |
81 | const char *sslHost = NULL; | |
866f8763 | 82 | const char *name = NULL; |
d8f41ccd A |
83 | CSSM_APPLE_TP_SSL_OPTIONS sslOpts; |
84 | CSSM_APPLE_TP_SMIME_OPTIONS smimeOpts; | |
85 | CSSM_APPLE_TP_ACTION_FLAGS actionFlags = 0; | |
86 | bool forceActionFlags = false; | |
87 | CSSM_APPLE_TP_ACTION_DATA actionData; | |
88 | CSSM_DATA optionData; | |
89 | CFDataRef cfActionData = NULL; | |
90 | SecTrustResultType resultType; | |
91 | OSStatus ocrtn; | |
866f8763 A |
92 | struct tm time; |
93 | CFGregorianDate gregorianDate; | |
94 | CFDateRef dateRef = NULL; | |
95 | CFOptionFlags revOptions = 0; | |
d8f41ccd A |
96 | |
97 | if(argc < 2) { | |
98 | return 2; /* @@@ Return 2 triggers usage message. */ | |
99 | } | |
100 | /* permit network cert fetch unless explicitly turned off with '-L' */ | |
101 | actionFlags |= CSSM_TP_ACTION_FETCH_CERT_FROM_NET; | |
102 | optind = 1; | |
866f8763 | 103 | while ((arg = getopt(argc, argv, "Cc:r:p:k:e:s:d:LlNnqR:")) != -1) { |
d8f41ccd | 104 | switch (arg) { |
866f8763 A |
105 | case 'C': |
106 | client = true; | |
107 | break; | |
d8f41ccd A |
108 | case 'c': |
109 | /* this can be specified multiple times */ | |
110 | if(addCertFile(optarg, &certs)) { | |
111 | ourRtn = 1; | |
112 | goto errOut; | |
113 | } | |
114 | break; | |
115 | case 'r': | |
116 | /* this can be specified multiple times */ | |
117 | if(addCertFile(optarg, &roots)) { | |
118 | ourRtn = 1; | |
119 | goto errOut; | |
120 | } | |
121 | break; | |
122 | case 'p': | |
123 | policy = policyStringToOid(optarg); | |
124 | if(policy == NULL) { | |
125 | ourRtn = 2; | |
126 | goto errOut; | |
127 | } | |
128 | break; | |
129 | case 'k': | |
130 | ortn = SecKeychainOpen(optarg, &kcRef); | |
131 | if(ortn) { | |
132 | cssmPerror("SecKeychainOpen", ortn); | |
133 | ourRtn = 1; | |
134 | goto errOut; | |
135 | } | |
136 | /* this can be specified multiple times */ | |
137 | if(keychains == NULL) { | |
138 | keychains = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); | |
139 | } | |
140 | CFArrayAppendValue(keychains, kcRef); | |
141 | CFRelease(kcRef); | |
142 | break; | |
143 | case 'L': | |
144 | actionFlags &= ~CSSM_TP_ACTION_FETCH_CERT_FROM_NET; | |
145 | forceActionFlags = true; | |
146 | break; | |
147 | case 'l': | |
148 | actionFlags |= CSSM_TP_ACTION_LEAF_IS_CA; | |
149 | break; | |
866f8763 A |
150 | case 'n': { |
151 | /* Legacy macOS used 'n' as the "no keychain search list" flag. | |
152 | iOS interprets it as the name option, with one argument. | |
153 | */ | |
154 | char *o = argv[optind]; | |
155 | if (o && o[0] != '-') { | |
156 | name = optarg; | |
157 | ++optind; | |
158 | break; | |
159 | } | |
160 | } /* intentional fall-through to "no keychains" case, if no arg */ | |
161 | case 'N': | |
d8f41ccd A |
162 | /* No keychains, signalled by empty keychain array */ |
163 | if(keychains != NULL) { | |
866f8763 | 164 | fprintf(stderr, "-k and -%c are mutually exclusive\n", arg); |
d8f41ccd A |
165 | ourRtn = 2; |
166 | goto errOut; | |
167 | } | |
168 | keychains = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); | |
169 | break; | |
170 | case 'e': | |
171 | emailAddrs = optarg; | |
172 | break; | |
173 | case 's': | |
174 | sslHost = optarg; | |
175 | break; | |
176 | case 'q': | |
177 | quiet = true; | |
178 | break; | |
866f8763 A |
179 | case 'd': |
180 | memset(&time, 0, sizeof(struct tm)); | |
181 | if (strptime(optarg, "%Y-%m-%d-%H:%M:%S", &time) == NULL) { | |
182 | if (strptime(optarg, "%Y-%m-%d", &time) == NULL) { | |
183 | fprintf(stderr, "Date processing error\n"); | |
184 | ourRtn = 2; | |
185 | goto errOut; | |
186 | } | |
187 | } | |
188 | gregorianDate.second = time.tm_sec; | |
189 | gregorianDate.minute = time.tm_min; | |
190 | gregorianDate.hour = time.tm_hour; | |
191 | gregorianDate.day = time.tm_mday; | |
192 | gregorianDate.month = time.tm_mon + 1; | |
193 | gregorianDate.year = time.tm_year + 1900; | |
194 | ||
195 | if (dateRef == NULL) { | |
196 | dateRef = CFDateCreate(NULL, CFGregorianDateGetAbsoluteTime(gregorianDate, NULL)); | |
197 | } | |
198 | break; | |
199 | case 'R': | |
200 | revOptions |= revCheckOptionStringToFlags(optarg); | |
201 | break; | |
d8f41ccd A |
202 | default: |
203 | ourRtn = 2; | |
204 | goto errOut; | |
205 | } | |
206 | } | |
207 | if(optind != argc) { | |
208 | ourRtn = 2; | |
209 | goto errOut; | |
210 | } | |
211 | ||
212 | if(certs == NULL) { | |
213 | if(roots == NULL) { | |
214 | fprintf(stderr, "***No certs specified.\n"); | |
215 | ourRtn = 2; | |
216 | goto errOut; | |
217 | } | |
218 | if(CFArrayGetCount(roots) != 1) { | |
219 | fprintf(stderr, "***Multiple roots and no certs not allowed.\n"); | |
220 | ourRtn = 2; | |
221 | goto errOut; | |
222 | } | |
223 | ||
224 | /* no certs and one root: verify the root */ | |
225 | certs = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); | |
226 | CFArrayAppendValue(certs, CFArrayGetValueAtIndex(roots, 0)); | |
227 | actionFlags |= CSSM_TP_ACTION_LEAF_IS_CA; | |
228 | } | |
229 | ||
230 | /* cook up a SecPolicyRef */ | |
231 | ortn = SecPolicySearchCreate(CSSM_CERT_X_509v3, | |
232 | policy, | |
233 | NULL, // policy opts | |
234 | &searchRef); | |
235 | if(ortn) { | |
236 | cssmPerror("SecPolicySearchCreate", ortn); | |
237 | ourRtn = 1; | |
238 | goto errOut; | |
239 | } | |
240 | ortn = SecPolicySearchCopyNext(searchRef, &policyRef); | |
241 | if(ortn) { | |
242 | cssmPerror("SecPolicySearchCopyNext", ortn); | |
243 | ourRtn = 1; | |
244 | goto errOut; | |
245 | } | |
246 | ||
247 | /* per-policy options */ | |
248 | if(compareOids(policy, &CSSMOID_APPLE_TP_SSL) || compareOids(policy, &CSSMOID_APPLE_TP_APPLEID_SHARING)) { | |
866f8763 A |
249 | const char *nameStr = (name) ? name : ((sslHost) ? sslHost : NULL); |
250 | if(nameStr) { | |
d8f41ccd A |
251 | memset(&sslOpts, 0, sizeof(sslOpts)); |
252 | sslOpts.Version = CSSM_APPLE_TP_SSL_OPTS_VERSION; | |
866f8763 A |
253 | sslOpts.ServerName = nameStr; |
254 | sslOpts.ServerNameLen = (uint32) strlen(nameStr); | |
255 | sslOpts.Flags = (client) ? CSSM_APPLE_TP_SSL_CLIENT : 0; | |
d8f41ccd A |
256 | optionData.Data = (uint8 *)&sslOpts; |
257 | optionData.Length = sizeof(sslOpts); | |
258 | ortn = SecPolicySetValue(policyRef, &optionData); | |
259 | if(ortn) { | |
260 | cssmPerror("SecPolicySetValue", ortn); | |
261 | ourRtn = 1; | |
262 | goto errOut; | |
263 | } | |
264 | } | |
265 | } | |
266 | if(compareOids(policy, &CSSMOID_APPLE_TP_SMIME)) { | |
866f8763 A |
267 | const char *nameStr = (name) ? name : ((emailAddrs) ? emailAddrs : NULL); |
268 | if(nameStr) { | |
d8f41ccd A |
269 | memset(&smimeOpts, 0, sizeof(smimeOpts)); |
270 | smimeOpts.Version = CSSM_APPLE_TP_SMIME_OPTS_VERSION; | |
866f8763 A |
271 | smimeOpts.SenderEmail = nameStr; |
272 | smimeOpts.SenderEmailLen = (uint32) strlen(nameStr); | |
d8f41ccd A |
273 | optionData.Data = (uint8 *)&smimeOpts; |
274 | optionData.Length = sizeof(smimeOpts); | |
275 | ortn = SecPolicySetValue(policyRef, &optionData); | |
276 | if(ortn) { | |
277 | cssmPerror("SecPolicySetValue", ortn); | |
278 | ourRtn = 1; | |
279 | goto errOut; | |
280 | } | |
281 | } | |
282 | } | |
283 | ||
866f8763 A |
284 | /* create policies array */ |
285 | policies = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); | |
286 | CFArrayAppendValue(policies, policyRef); | |
287 | /* add optional SecPolicyRef for revocation, if specified */ | |
288 | if(revOptions != 0) { | |
289 | revPolicyRef = SecPolicyCreateRevocation(revOptions); | |
290 | CFArrayAppendValue(policies, revPolicyRef); | |
291 | } | |
292 | ||
293 | /* create trust reference from certs and policies */ | |
294 | ortn = SecTrustCreateWithCertificates(certs, policies, &trustRef); | |
d8f41ccd A |
295 | if(ortn) { |
296 | cssmPerror("SecTrustCreateWithCertificates", ortn); | |
297 | ourRtn = 1; | |
298 | goto errOut; | |
299 | } | |
300 | ||
301 | /* roots (anchors) are optional */ | |
302 | if(roots != NULL) { | |
303 | ortn = SecTrustSetAnchorCertificates(trustRef, roots); | |
304 | if(ortn) { | |
305 | cssmPerror("SecTrustSetAnchorCertificates", ortn); | |
306 | ourRtn = 1; | |
307 | goto errOut; | |
308 | } | |
309 | } | |
310 | if(actionFlags || forceActionFlags) { | |
311 | memset(&actionData, 0, sizeof(actionData)); | |
312 | actionData.Version = CSSM_APPLE_TP_ACTION_VERSION; | |
313 | actionData.ActionFlags = actionFlags; | |
314 | cfActionData = CFDataCreate(NULL, (UInt8 *)&actionData, sizeof(actionData)); | |
315 | ortn = SecTrustSetParameters(trustRef, CSSM_TP_ACTION_DEFAULT, cfActionData); | |
316 | if(ortn) { | |
317 | cssmPerror("SecTrustSetParameters", ortn); | |
318 | ourRtn = 1; | |
319 | goto errOut; | |
320 | } | |
321 | } | |
322 | if(keychains) { | |
323 | ortn = SecTrustSetKeychains(trustRef, keychains); | |
324 | if(ortn) { | |
325 | cssmPerror("SecTrustSetKeychains", ortn); | |
326 | ourRtn = 1; | |
327 | goto errOut; | |
328 | } | |
329 | } | |
866f8763 A |
330 | if(dateRef != NULL) { |
331 | ortn = SecTrustSetVerifyDate(trustRef, dateRef); | |
332 | if(ortn) { | |
333 | cssmPerror("SecTrustSetVerifyDate", ortn); | |
334 | ourRtn = 1; | |
335 | goto errOut; | |
336 | } | |
337 | } | |
d8f41ccd A |
338 | |
339 | /* GO */ | |
340 | ortn = SecTrustEvaluate(trustRef, &resultType); | |
341 | if(ortn) { | |
342 | /* should never fail - error on this doesn't mean the cert verified badly */ | |
343 | cssmPerror("SecTrustEvaluate", ortn); | |
344 | ourRtn = 1; | |
345 | goto errOut; | |
346 | } | |
347 | switch(resultType) { | |
348 | case kSecTrustResultUnspecified: | |
349 | /* cert chain valid, no special UserTrust assignments */ | |
350 | case kSecTrustResultProceed: | |
351 | /* cert chain valid AND user explicitly trusts this */ | |
352 | break; | |
353 | case kSecTrustResultDeny: | |
354 | if(!quiet) { | |
355 | fprintf(stderr, "SecTrustEvaluate result: kSecTrustResultDeny\n"); | |
356 | } | |
357 | ourRtn = 1; | |
358 | break; | |
d8f41ccd A |
359 | default: |
360 | ourRtn = 1; | |
361 | if(!quiet) { | |
362 | /* See what the TP had to say about this */ | |
363 | ortn = SecTrustGetCssmResultCode(trustRef, &ocrtn); | |
364 | if(ortn) { | |
365 | cssmPerror("SecTrustGetCssmResultCode", ortn); | |
366 | } | |
367 | else { | |
368 | cssmPerror("Cert Verify Result", ocrtn); | |
369 | } | |
370 | } | |
371 | break; | |
372 | } | |
373 | ||
374 | if((ourRtn == 0) & !quiet) { | |
375 | printf("...certificate verification successful.\n"); | |
376 | } | |
377 | errOut: | |
866f8763 | 378 | CFReleaseNull(dateRef); |
d8f41ccd A |
379 | /* cleanup */ |
380 | CFRELEASE(certs); | |
381 | CFRELEASE(roots); | |
382 | CFRELEASE(keychains); | |
866f8763 A |
383 | CFRELEASE(policies); |
384 | CFRELEASE(revPolicyRef); | |
d8f41ccd A |
385 | CFRELEASE(policyRef); |
386 | CFRELEASE(trustRef); | |
387 | CFRELEASE(searchRef); | |
388 | CFRELEASE(cfActionData); | |
389 | return ourRtn; | |
390 | } |