]> git.saurik.com Git - apple/security.git/blob - SecurityTool/identity_find.m
Security-58286.270.3.0.1.tar.gz
[apple/security.git] / SecurityTool / identity_find.m
1 /*
2 * Copyright (c) 2003-2010,2012,2014 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 * identity_find.c
24 */
25
26 #import <Foundation/Foundation.h>
27
28 #include "identity_find.h"
29 #include "keychain_utilities.h"
30 #include "trusted_cert_utils.h"
31 #include "security_tool.h"
32
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <string.h>
36 #include <unistd.h>
37 #include <Security/cssmtype.h>
38 #include <Security/oidsalg.h>
39 #include <Security/SecCertificate.h>
40 #include <Security/SecIdentity.h>
41 #include <Security/SecIdentitySearch.h>
42 #include <Security/SecPolicySearch.h>
43 #include <Security/SecTrust.h>
44 #include <Security/SecItem.h>
45 #include <Security/SecItemPriv.h>
46 #include <utilities/SecCFWrappers.h>
47
48 // cssmErrorString
49 #include <Security/SecBasePriv.h>
50 // SecCertificateInferLabel, SecDigestGetData
51 #include <Security/SecCertificatePriv.h>
52 // SecIdentitySearchCreateWithPolicy
53 #include <Security/SecIdentitySearchPriv.h>
54
55 static bool checkKeyUsageOperation(CSSM_KEYUSE keyUse, CSSM_KEYUSE keyOp, NSDictionary *keyAttrs, id secKeyOp) {
56 if (keyUse & keyOp) {
57 return [keyAttrs[secKeyOp] isEqual:@YES];
58 }
59 return true;
60 }
61
62 static bool checkKeyUsage(CSSM_KEYUSE keyUse, NSDictionary *keyAttrs) {
63 CSSM_KEYUSE keyOps[] = { CSSM_KEYUSE_ENCRYPT, CSSM_KEYUSE_DECRYPT, CSSM_KEYUSE_SIGN, CSSM_KEYUSE_VERIFY, CSSM_KEYUSE_SIGN_RECOVER, CSSM_KEYUSE_VERIFY_RECOVER, CSSM_KEYUSE_WRAP, CSSM_KEYUSE_UNWRAP, CSSM_KEYUSE_DERIVE };
64 id secKeyOps[] = { (id)kSecAttrCanEncrypt, (id)kSecAttrCanDecrypt, (id)kSecAttrCanSign, (id)kSecAttrCanVerify, (id)kSecAttrCanSignRecover, (id)kSecAttrCanVerifyRecover, (id)kSecAttrCanWrap, (id)kSecAttrCanUnwrap, (id)kSecAttrCanDerive };
65
66 if (keyUse & CSSM_KEYUSE_ANY) {
67 for (size_t i = 0; i < sizeof(keyOps) / sizeof(CSSM_KEYUSE); ++i) {
68 keyUse |= keyOps[i];
69 }
70 }
71 for (size_t i = 0; i < sizeof(keyOps) / sizeof(CSSM_KEYUSE); ++i) {
72 if (!checkKeyUsageOperation(keyUse, keyOps[i], keyAttrs, secKeyOps[i]))
73 return false;
74 }
75 return true;
76 }
77
78 SecIdentityRef
79 CopyMatchingIdentity(CFTypeRef keychainOrArray,
80 const char *identity,
81 const char *hash,
82 CSSM_KEYUSE keyUsage)
83 {
84 id identityRef;
85 // check input hash string and convert to data
86 NSData *hashData;
87 if (hash) {
88 hashData = (__bridge_transfer NSData *)CFDataCreateFromHexString(kCFAllocatorDefault, (__bridge CFStringRef)[NSString stringWithUTF8String:hash]);
89 }
90
91 // filter candidates against the hash (or the name, if no hash provided)
92 NSString *matchName = (identity) ? [NSString stringWithUTF8String:identity] : nil;
93 Boolean exactMatch = FALSE;
94
95 NSDictionary *query;
96 if (keychainOrArray) {
97 id keychainArray;
98 if (![(__bridge id)keychainOrArray isKindOfClass:[NSArray class]]) {
99 keychainArray = @[(__bridge id)keychainOrArray];
100 } else {
101 keychainArray = (__bridge id)keychainOrArray;
102 }
103 query = @{ (id)kSecClass : (id)kSecClassIdentity,
104 (id)kSecMatchLimit : (id)kSecMatchLimitAll,
105 (id)kSecMatchSearchList : keychainArray,
106 (id)kSecReturnRef : @YES };
107 } else {
108 query = @{ (id)kSecClass : (id)kSecClassIdentity,
109 (id)kSecMatchLimit : (id)kSecMatchLimitAll,
110 (id)kSecReturnRef : @YES };
111 }
112 NSArray *identities;
113 if (SecItemCopyMatching((CFDictionaryRef)query, (void *)&identities) != errSecSuccess) {
114 return NULL;
115 }
116
117 for (id candidate in identities) {
118 id cert;
119 if (SecIdentityCopyCertificate((__bridge SecIdentityRef)candidate, (void *)&cert) != errSecSuccess)
120 continue;
121 if (keyUsage) {
122 NSData *pubKeyHash = (__bridge_transfer NSData*)SecCertificateCopyPublicKeySHA1Digest((__bridge SecCertificateRef)cert);
123 NSDictionary *keyAttrs;
124 if (SecItemCopyMatching((__bridge CFDictionaryRef)@{ (id)kSecClass : (id)kSecClassKey, (id)kSecAttrApplicationLabel : pubKeyHash, (id)kSecReturnAttributes : @YES }, (void *)&keyAttrs) != errSecSuccess)
125 continue;
126 if (!(checkKeyUsage(keyUsage, keyAttrs))) {
127 continue;
128 }
129 }
130 if (hashData) {
131 NSData *certDataHash = (__bridge NSData *)SecCertificateGetSHA1Digest((__bridge SecCertificateRef)cert);
132 if ([hashData isEqual:certDataHash]) {
133 identityRef = candidate;
134 break;
135 }
136 } else {
137 // copy certificate name
138 NSString *commonName;
139 if ((SecCertificateCopyCommonName((__bridge SecCertificateRef)cert, (void *)&commonName) != errSecSuccess) || commonName == nil) {
140 continue; // no name, so no match is possible
141 }
142
143 if (identity && !strcmp(identity, "*")) { // special case: means "just take the first one"
144 sec_error("Using identity \"%s\"", [commonName UTF8String]);
145 identityRef = candidate;
146 break;
147 }
148 CFRange find = { kCFNotFound, 0 };
149 if (commonName && matchName)
150 find = CFStringFind((__bridge CFStringRef)commonName, (__bridge CFStringRef)matchName, kCFCompareCaseInsensitive | kCFCompareNonliteral);
151 Boolean isExact = (find.location == 0 && find.length == (CFIndex)commonName.length);
152 if (find.location == kCFNotFound) {
153 continue; // no match
154 }
155 if (identityRef) { // got two matches
156 if (exactMatch && !isExact) { // prior is better; ignore this one
157 continue;
158 }
159 if (exactMatch == isExact) { // same class of match
160 if ([identityRef isEqual:candidate]) { // identities have same cert
161 continue;
162 }
163 // ambiguity - must fail
164 sec_error("\"%s\" is ambiguous, matches more than one certificate", identity);
165 identityRef = nil;
166 break;
167 }
168 }
169 identityRef = candidate;
170 exactMatch = isExact;
171 }
172 }
173
174 return identityRef?(__bridge_retained SecIdentityRef)identityRef:NULL;
175 }
176
177 static void printIdentity(SecIdentityRef identity, SecPolicyRef policy, int ordinalValue)
178 {
179 OSStatus status;
180 Boolean printHash = TRUE, printName = TRUE;
181 SecCertificateRef cert = NULL;
182
183 status = SecIdentityCopyCertificate(identity, &cert);
184 if (!status)
185 {
186 CSSM_DATA certData = { 0, nil };
187 (void) SecCertificateGetData(cert, &certData);
188 fprintf(stdout, "%3d) ", ordinalValue);
189 if (printHash) {
190 uint8 sha1_hash[20];
191 CSSM_DATA digest;
192 digest.Length = sizeof(sha1_hash);
193 digest.Data = sha1_hash;
194 if (SecDigestGetData(CSSM_ALGID_SHA1, &digest, &certData) == CSSM_OK) {
195 unsigned int i;
196 size_t len = digest.Length;
197 uint8 *cp = digest.Data;
198 for(i=0; i<len; i++) {
199 fprintf(stdout, "%02X", ((unsigned char *)cp)[i]);
200 }
201 } else {
202 fprintf(stdout, "!----- unable to get SHA-1 digest -----!");
203 }
204 }
205 if (printName) {
206 char *nameBuf = NULL;
207 CFStringRef nameRef = NULL;
208 status = SecCertificateInferLabel(cert, &nameRef);
209 CFIndex nameLen = (nameRef) ? CFStringGetLength(nameRef) : 0;
210 if (nameLen > 0) {
211 CFIndex bufLen = 1 + CFStringGetMaximumSizeForEncoding(nameLen, kCFStringEncodingUTF8);
212 nameBuf = (char *)malloc(bufLen);
213 if (!CFStringGetCString(nameRef, nameBuf, bufLen-1, kCFStringEncodingUTF8))
214 nameBuf[0]=0;
215 }
216 fprintf(stdout, " \"%s\"", (nameBuf && nameBuf[0] != 0) ? nameBuf : "<unknown>");
217 if (nameBuf)
218 free(nameBuf);
219 safe_CFRelease(&nameRef);
220 }
221
222 // Default to X.509 Basic if no policy was specified
223 if (!policy) {
224 SecPolicySearchRef policySearch = NULL;
225 if (SecPolicySearchCreate(CSSM_CERT_X_509v3, &CSSMOID_APPLE_X509_BASIC, NULL, &policySearch)==noErr) {
226 SecPolicySearchCopyNext(policySearch, &policy);
227 }
228 safe_CFRelease(&policySearch);
229 } else {
230 CFRetain(policy);
231 }
232
233 // Create the trust reference, given policy and certificates
234 SecTrustRef trust = nil;
235 SecTrustResultType trustResult = kSecTrustResultInvalid;
236 OSStatus trustResultCode = noErr;
237 CFMutableArrayRef certificates = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
238 if (certificates) {
239 CFArrayAppendValue(certificates, cert);
240 }
241 status = SecTrustCreateWithCertificates((CFArrayRef)certificates, policy, &trust);
242 if (!status) {
243 status = SecTrustEvaluate(trust, &trustResult);
244 }
245 if (trustResult != kSecTrustResultInvalid) {
246 status = SecTrustGetCssmResultCode(trust, &trustResultCode);
247 }
248 if (trustResultCode != noErr) {
249 fprintf(stdout, " (%s)\n", cssmErrorString(trustResultCode));
250 } else {
251 fprintf(stdout, "\n");
252 }
253 safe_CFRelease(&trust);
254 safe_CFRelease(&policy);
255 safe_CFRelease(&certificates);
256 }
257 safe_CFRelease(&cert);
258 (void) status;
259 }
260
261 static void
262 do_identity_search_with_policy(CFTypeRef keychainOrArray,
263 const char *name,
264 const CSSM_OID* oidPtr,
265 CSSM_KEYUSE keyUsage,
266 Boolean client,
267 Boolean validOnly)
268 {
269 // set up SMIME options with provided data
270 CE_KeyUsage ceKeyUsage = 0;
271 if (keyUsage & CSSM_KEYUSE_SIGN) ceKeyUsage |= CE_KU_DigitalSignature;
272 if (keyUsage & CSSM_KEYUSE_ENCRYPT) ceKeyUsage |= CE_KU_KeyEncipherment;
273 CSSM_APPLE_TP_SMIME_OPTIONS smimeOpts = {
274 CSSM_APPLE_TP_SMIME_OPTS_VERSION, // Version
275 ceKeyUsage, // IntendedUsage
276 name ? (uint32) strlen(name) : 0, // SenderEmailLen
277 name // SenderEmail
278 };
279 CSSM_DATA smimeValue = { sizeof(smimeOpts), (uint8*)&smimeOpts };
280
281 // set up SSL options with provided data
282 CSSM_APPLE_TP_SSL_OPTIONS sslOpts = {
283 CSSM_APPLE_TP_SSL_OPTS_VERSION, // Version
284 (name && !client) ? (uint32) strlen(name) : 0, // ServerNameLen
285 (client) ? NULL : name, // ServerName
286 (client) ? CSSM_APPLE_TP_SSL_CLIENT : 0 // Flags
287 };
288 CSSM_DATA sslValue = { sizeof(sslOpts), (uint8*)&sslOpts };
289
290 // get a policy ref for the specified policy OID
291 OSStatus status = noErr;
292 SecPolicyRef policy = NULL;
293 SecPolicySearchRef policySearch = NULL;
294 status = SecPolicySearchCreate(CSSM_CERT_X_509v3, oidPtr, NULL, &policySearch);
295 if (!status)
296 status = SecPolicySearchCopyNext(policySearch, &policy);
297
298 CSSM_DATA *policyValue = NULL;
299 const char *policyName = "<unknown>";
300
301 if (compareOids(oidPtr, &CSSMOID_APPLE_TP_SMIME)) {
302 policyName = "S/MIME";
303 policyValue = &smimeValue;
304 }
305 else if (compareOids(oidPtr, &CSSMOID_APPLE_TP_SSL)) {
306 if (client)
307 policyName = "SSL (client)";
308 else
309 policyName = "SSL (server)";
310 policyValue = &sslValue;
311 }
312 else if (compareOids(oidPtr, &CSSMOID_APPLE_TP_EAP)) {
313 policyName = "EAP";
314 }
315 else if (compareOids(oidPtr, &CSSMOID_APPLE_TP_IP_SEC)) {
316 policyName = "IPsec";
317 }
318 else if (compareOids(oidPtr, &CSSMOID_APPLE_TP_ICHAT)) {
319 policyName = "iChat";
320 }
321 else if (compareOids(oidPtr, &CSSMOID_APPLE_TP_CODE_SIGNING)) {
322 policyName = "Code Signing";
323 }
324 else if (compareOids(oidPtr, &CSSMOID_APPLE_X509_BASIC)) {
325 policyName = "X.509 Basic";
326 }
327 else if (compareOids(oidPtr, &CSSMOID_APPLE_TP_MACAPPSTORE_RECEIPT)) {
328 policyName = "Mac App Store Receipt";
329 }
330 else if (compareOids(oidPtr, &CSSMOID_APPLE_TP_APPLEID_SHARING)) {
331 policyName = "AppleID Sharing";
332 }
333
334 // set the policy's value, if there is one (this is specific to certain policies)
335 if (policy && policyValue)
336 status = SecPolicySetValue(policy, policyValue);
337
338 CFStringRef idStr = (name) ? CFStringCreateWithCStringNoCopy(NULL, name, kCFStringEncodingUTF8, kCFAllocatorNull) : NULL;
339 SecIdentitySearchRef searchRef = NULL;
340 int identityCount = 0;
341
342 if (!validOnly) {
343 // create an identity search, specifying all identities (i.e. returnOnlyValidIdentities=FALSE)
344 // this should return all identities which match the policy and key usage, regardless of validity
345 fprintf(stdout, "\nPolicy: %s\n", policyName);
346 fprintf(stdout, " Matching identities\n");
347 status = SecIdentitySearchCreateWithPolicy(policy, idStr, keyUsage, keychainOrArray, FALSE, &searchRef);
348 if (!status)
349 {
350 SecIdentityRef identityRef = NULL;
351 while (SecIdentitySearchCopyNext(searchRef, &identityRef) == noErr)
352 {
353 identityCount++;
354 printIdentity(identityRef, policy, identityCount);
355 safe_CFRelease(&identityRef);
356 }
357 safe_CFRelease(&searchRef);
358 }
359 fprintf(stdout, " %d identities found\n\n", identityCount);
360 }
361
362 // create a second identity search, specifying only valid identities (i.e. returnOnlyValidIdentities=TRUE)
363 // this should return only valid identities for the policy.
364 identityCount = 0;
365 if (!validOnly) {
366 fprintf(stdout, " Valid identities only\n");
367 }
368 status = SecIdentitySearchCreateWithPolicy(policy, idStr, keyUsage, keychainOrArray, TRUE, &searchRef);
369 if (!status)
370 {
371 SecIdentityRef identityRef = NULL;
372 while (SecIdentitySearchCopyNext(searchRef, &identityRef) == noErr)
373 {
374 identityCount++;
375 printIdentity(identityRef, policy, identityCount);
376 safe_CFRelease(&identityRef);
377 }
378 safe_CFRelease(&searchRef);
379 }
380 fprintf(stdout, " %d valid identities found\n", identityCount);
381
382 safe_CFRelease(&idStr);
383 safe_CFRelease(&policy);
384 safe_CFRelease(policySearch);
385 }
386
387 static void
388 do_system_identity_search(CFStringRef domain)
389 {
390 SecIdentityRef identity = NULL;
391 OSStatus status = SecIdentityCopySystemIdentity(domain, &identity, NULL);
392 if (CFEqual(domain, kSecIdentityDomainDefault)) {
393 fprintf(stdout, "\n System default identity\n");
394 } else if (CFEqual(domain, kSecIdentityDomainKerberosKDC)) {
395 fprintf(stdout, "\n System Kerberos KDC identity\n");
396 }
397 if (!status && identity) {
398 SecPolicyRef policy = NULL;
399 SecPolicySearchRef policySearch = NULL;
400 if (SecPolicySearchCreate(CSSM_CERT_X_509v3, &CSSMOID_APPLE_X509_BASIC, NULL, &policySearch) == noErr) {
401 if (SecPolicySearchCopyNext(policySearch, &policy) == noErr) {
402 printIdentity(identity, policy, 1);
403 CFRelease(policy);
404 }
405 safe_CFRelease(&policySearch);
406 }
407 safe_CFRelease(&identity);
408 }
409 }
410
411 static int
412 do_find_identities(CFTypeRef keychainOrArray, const char *name, unsigned int policyFlags, Boolean validOnly)
413 {
414 int result = 0;
415
416 if (name) {
417 fprintf(stdout, "Looking for identities matching \"%s\"\n", name);
418 }
419 if (policyFlags & (1 << 0))
420 do_identity_search_with_policy(keychainOrArray, name, &CSSMOID_APPLE_TP_SSL, CSSM_KEYUSE_SIGN, TRUE, validOnly);
421 if (policyFlags & (1 << 1))
422 do_identity_search_with_policy(keychainOrArray, name, &CSSMOID_APPLE_TP_SSL, CSSM_KEYUSE_SIGN, FALSE, validOnly);
423 if (policyFlags & (1 << 2))
424 do_identity_search_with_policy(keychainOrArray, name, &CSSMOID_APPLE_TP_SMIME, CSSM_KEYUSE_SIGN, TRUE, validOnly);
425 if (policyFlags & (1 << 3))
426 do_identity_search_with_policy(keychainOrArray, name, &CSSMOID_APPLE_TP_EAP, CSSM_KEYUSE_SIGN, TRUE, validOnly);
427 if (policyFlags & (1 << 4))
428 do_identity_search_with_policy(keychainOrArray, name, &CSSMOID_APPLE_TP_IP_SEC, CSSM_KEYUSE_SIGN, TRUE, validOnly);
429 if (policyFlags & (1 << 5))
430 do_identity_search_with_policy(keychainOrArray, name, &CSSMOID_APPLE_TP_ICHAT, CSSM_KEYUSE_SIGN, TRUE, validOnly);
431 if (policyFlags & (1 << 6))
432 do_identity_search_with_policy(keychainOrArray, name, &CSSMOID_APPLE_TP_CODE_SIGNING, CSSM_KEYUSE_SIGN, TRUE, validOnly);
433 if (policyFlags & (1 << 7))
434 do_identity_search_with_policy(keychainOrArray, name, &CSSMOID_APPLE_X509_BASIC, CSSM_KEYUSE_SIGN, TRUE, validOnly);
435
436 if (policyFlags & (1 << 8))
437 do_system_identity_search(kSecIdentityDomainDefault);
438 if (policyFlags & (1 << 9))
439 do_system_identity_search(kSecIdentityDomainKerberosKDC);
440 if (policyFlags & (1 << 10))
441 do_identity_search_with_policy(keychainOrArray, name, &CSSMOID_APPLE_TP_APPLEID_SHARING, CSSM_KEYUSE_SIGN, FALSE, validOnly);
442 if (policyFlags & (1 << 11))
443 do_identity_search_with_policy(keychainOrArray, name, &CSSMOID_APPLE_TP_MACAPPSTORE_RECEIPT, CSSM_KEYUSE_SIGN, TRUE, validOnly);
444
445 return result;
446 }
447
448 int
449 keychain_find_identity(int argc, char * const *argv)
450 {
451 int ch, result = 0;
452 unsigned int policyFlags = 0;
453 const char *name = NULL;
454 Boolean validOnly = FALSE;
455 CFTypeRef keychainOrArray = NULL;
456
457 /*
458 * " -p Specify policy to evaluate (multiple -p options are allowed)\n"
459 * " -s Specify optional policy-specific string (e.g. a DNS hostname for SSL,\n"
460 * " or RFC822 email address for S/MIME)\n"
461 * " -v Show valid identities only (default is to show all identities)\n"
462 */
463
464 while ((ch = getopt(argc, argv, "hp:s:v")) != -1)
465 {
466 switch (ch)
467 {
468 case 'p':
469 if (optarg != NULL) {
470 if (!strcmp(optarg, "ssl-client"))
471 policyFlags |= 1 << 0;
472 else if (!strcmp(optarg, "ssl-server"))
473 policyFlags |= 1 << 1;
474 else if (!strcmp(optarg, "smime"))
475 policyFlags |= 1 << 2;
476 else if (!strcmp(optarg, "eap"))
477 policyFlags |= 1 << 3;
478 else if (!strcmp(optarg, "ipsec"))
479 policyFlags |= 1 << 4;
480 else if (!strcmp(optarg, "ichat"))
481 policyFlags |= 1 << 5;
482 else if (!strcmp(optarg, "codesigning"))
483 policyFlags |= 1 << 6;
484 else if (!strcmp(optarg, "basic"))
485 policyFlags |= 1 << 7;
486 else if (!strcmp(optarg, "sys-default"))
487 policyFlags |= 1 << 8;
488 else if (!strcmp(optarg, "sys-kerberos-kdc"))
489 policyFlags |= 1 << 9;
490 else if (!strcmp(optarg, "appleID"))
491 policyFlags |= 1 << 10;
492 else if (!strcmp(optarg, "macappstore"))
493 policyFlags |= 1 << 11;
494 else {
495 result = SHOW_USAGE_MESSAGE;
496 goto cleanup;
497 }
498 }
499 break;
500 case 's':
501 name = optarg;
502 break;
503 case 'v':
504 validOnly = TRUE;
505 break;
506 case '?':
507 default:
508 result = 2; /* @@@ Return 2 triggers usage message. */
509 goto cleanup;
510 }
511 }
512
513 if (!policyFlags)
514 policyFlags |= 1 << 7; /* default to basic policy if none specified */
515
516 argc -= optind;
517 argv += optind;
518
519 keychainOrArray = keychain_create_array(argc, argv);
520
521 result = do_find_identities(keychainOrArray, name, policyFlags, validOnly);
522
523 cleanup:
524 safe_CFRelease(&keychainOrArray);
525
526 return result;
527 }
528