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