1/* 2 * Copyright (c) 2004 Apple Computer, 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/* 24 * identPicker.cpp - Given a keychain, select from possible multiple 25 * SecIdentityRefs via stdio UI, and cook up a 26 * CFArray containing that identity and all certs needed 27 * for cert verification by an SSL peer. The resulting 28 * CFArrayRef is suitable for passing to SSLSetCertificate(). 29 */ 30 31#include "identPicker.h" 32#include <sys/param.h> 33#include <string.h> 34#include <stdio.h> 35#include <ctype.h> 36 37/* 38 * Safe gets(). 39 * -- guaranteed no buffer overflow 40 * -- guaranteed NULL-terminated string 41 * -- handles empty string (i.e., response is just CR) properly 42 */ 43static void getString( 44 char *buf, 45 unsigned bufSize) 46{ 47 unsigned dex; 48 char c; 49 char *cp = buf; 50 51 for(dex=0; dex<bufSize-1; dex++) { 52 c = getchar(); 53 if(!isprint(c)) { 54 break; 55 } 56 switch(c) { 57 case '\n': 58 case '\r': 59 goto done; 60 default: 61 *cp++ = c; 62 } 63 } 64done: 65 *cp = '\0'; 66} 67 68/* 69 * Obtain the printable name of a SecKeychainItemRef as a C string. 70 * Caller must free() the result. 71 */ 72static char *kcItemPrintableName( 73 SecKeychainItemRef certRef) 74{ 75 char *crtn = NULL; 76 77 /* just search for the one attr we want */ 78 UInt32 tag = kSecLabelItemAttr; 79 SecKeychainAttributeInfo attrInfo; 80 attrInfo.count = 1; 81 attrInfo.tag = &tag; 82 attrInfo.format = NULL; 83 SecKeychainAttributeList *attrList = NULL; 84 SecKeychainAttribute *attr = NULL; 85 86 OSStatus ortn = SecKeychainItemCopyAttributesAndData( 87 (SecKeychainItemRef)certRef, 88 &attrInfo, 89 NULL, // itemClass 90 &attrList, 91 NULL, // length - don't need the data 92 NULL); // outData 93 if(ortn) { 94 cssmPerror("SecKeychainItemCopyAttributesAndData", ortn); 95 /* may want to be a bit more robust here, but this should 96 * never happen */ 97 return strdup("Unnamed KeychainItem"); 98 } 99 /* subsequent errors to errOut: */ 100 101 if((attrList == NULL) || (attrList->count != 1)) { 102 printf("***Unexpected result fetching label attr\n"); 103 crtn = strdup("Unnamed KeychainItem"); 104 goto errOut; 105 } 106 /* We're assuming 8-bit ASCII attribute data here... */ 107 attr = attrList->attr; 108 crtn = (char *)malloc(attr->length + 1); 109 memmove(crtn, attr->data, attr->length); 110 crtn[attr->length] = '\0'; 111 112errOut: 113 SecKeychainItemFreeAttributesAndData(attrList, NULL); 114 return crtn; 115} 116 117/* 118 * Get the final term of a keychain's path as a C string. Caller must free() 119 * the result. 120 */ 121static char *kcFileName( 122 SecKeychainRef kcRef) 123{ 124 char fullPath[MAXPATHLEN + 1]; 125 OSStatus ortn; 126 UInt32 pathLen = MAXPATHLEN; 127 128 ortn = SecKeychainGetPath(kcRef, &pathLen, fullPath); 129 if(ortn) { 130 cssmPerror("SecKeychainGetPath", ortn); 131 return strdup("orphan keychain"); 132 } 133 134 /* NULL terminate the path string and search for final '/' */ 135 fullPath[pathLen] = '\0'; 136 char *lastSlash = NULL; 137 char *thisSlash = fullPath; 138 do { 139 thisSlash = strchr(thisSlash, '/'); 140 if(thisSlash == NULL) { 141 /* done */ 142 break; 143 } 144 thisSlash++; 145 lastSlash = thisSlash; 146 } while(thisSlash != NULL); 147 if(lastSlash == NULL) { 148 /* no slashes, odd, but handle it */ 149 return strdup(fullPath); 150 } 151 else { 152 return strdup(lastSlash); 153 } 154} 155 156/* 157 * Determine if specified SecCertificateRef is a self-signed cert. 158 * We do this by comparing the subject and issuerr names; no cryptographic 159 * verification is performed. 160 * 161 * Returns true if the cert appears to be a root. 162 */ 163static bool isCertRefRoot( 164 SecCertificateRef certRef) 165{ 166 /* just search for the two attrs we want */ 167 UInt32 tags[2] = {kSecSubjectItemAttr, kSecIssuerItemAttr}; 168 SecKeychainAttributeInfo attrInfo; 169 attrInfo.count = 2; 170 attrInfo.tag = tags; 171 attrInfo.format = NULL; 172 SecKeychainAttributeList *attrList = NULL; 173 SecKeychainAttribute *attr1 = NULL; 174 SecKeychainAttribute *attr2 = NULL; 175 bool brtn = false; 176 177 OSStatus ortn = SecKeychainItemCopyAttributesAndData( 178 (SecKeychainItemRef)certRef, 179 &attrInfo, 180 NULL, // itemClass 181 &attrList, 182 NULL, // length - don't need the data 183 NULL); // outData 184 if(ortn) { 185 cssmPerror("SecKeychainItemCopyAttributesAndData", ortn); 186 /* may want to be a bit more robust here, but this should 187 * never happen */ 188 return false; 189 } 190 /* subsequent errors to errOut: */ 191 192 if((attrList == NULL) || (attrList->count != 2)) { 193 printf("***Unexpected result fetching label attr\n"); 194 goto errOut; 195 } 196 197 /* rootness is just byte-for-byte compare of the two names */ 198 attr1 = &attrList->attr[0]; 199 attr2 = &attrList->attr[1]; 200 if(attr1->length == attr2->length) { 201 if(memcmp(attr1->data, attr2->data, attr1->length) == 0) { 202 brtn = true; 203 } 204 } 205errOut: 206 SecKeychainItemFreeAttributesAndData(attrList, NULL); 207 return brtn; 208} 209 210 211/* 212 * Given a SecIdentityRef, do our best to construct a complete, ordered, and 213 * verified cert chain, returning the result in a CFArrayRef. The result is 214 * suitable for use when calling SSLSetCertificate(). 215 */ 216static OSStatus completeCertChain( 217 SecIdentityRef identity, 218 SecCertificateRef trustedAnchor, // optional additional trusted anchor 219 bool includeRoot, // include the root in outArray 220 CFArrayRef *outArray) // created and RETURNED 221{ 222 CFMutableArrayRef certArray; 223 SecTrustRef secTrust = NULL; 224 SecPolicyRef policy = NULL; 225 SecPolicySearchRef policySearch = NULL; 226 SecTrustResultType secTrustResult; 227 CSSM_TP_APPLE_EVIDENCE_INFO *dummyEv; // not used 228 CFArrayRef certChain = NULL; // constructed chain 229 CFIndex numResCerts; 230 231 certArray = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); 232 CFArrayAppendValue(certArray, identity); 233 234 /* 235 * Case 1: identity is a root; we're done. Note that this case 236 * overrides the includeRoot argument. 237 */ 238 SecCertificateRef certRef; 239 OSStatus ortn = SecIdentityCopyCertificate(identity, &certRef); 240 if(ortn) { 241 /* should never happen */ 242 cssmPerror("SecIdentityCopyCertificate", ortn); 243 return ortn; 244 } 245 bool isRoot = isCertRefRoot(certRef); 246 if(isRoot) { 247 *outArray = certArray; 248 CFRelease(certRef); 249 return noErr; 250 } 251 252 /* 253 * Now use SecTrust to get a complete cert chain, using all of the 254 * user's keychains to look for intermediate certs. 255 * NOTE this does NOT handle root certs which are not in the system 256 * root cert DB. (The above case, where the identity is a root cert, does.) 257 */ 258 CFMutableArrayRef subjCerts = CFArrayCreateMutable(NULL, 1, &kCFTypeArrayCallBacks); 259 CFArraySetValueAtIndex(subjCerts, 0, certRef); 260 261 /* the array owns the subject cert ref now */ 262 CFRelease(certRef); 263 264 /* Get a SecPolicyRef for generic X509 cert chain verification */ 265 ortn = SecPolicySearchCreate(CSSM_CERT_X_509v3, 266 &CSSMOID_APPLE_X509_BASIC, 267 NULL, // value 268 &policySearch); 269 if(ortn) { 270 cssmPerror("SecPolicySearchCreate", ortn); 271 goto errOut; 272 } 273 ortn = SecPolicySearchCopyNext(policySearch, &policy); 274 if(ortn) { 275 cssmPerror("SecPolicySearchCopyNext", ortn); 276 goto errOut; 277 } 278 279 /* build a SecTrustRef for specified policy and certs */ 280 ortn = SecTrustCreateWithCertificates(subjCerts, 281 policy, &secTrust); 282 if(ortn) { 283 cssmPerror("SecTrustCreateWithCertificates", ortn); 284 goto errOut; 285 } 286 287 if(trustedAnchor) { 288 /* 289 * Tell SecTrust to trust this one in addition to the current 290 * trusted system-wide anchors. 291 */ 292 CFMutableArrayRef newAnchors; 293 CFArrayRef currAnchors; 294 295 ortn = SecTrustCopyAnchorCertificates(&currAnchors); 296 if(ortn) { 297 /* should never happen */ 298 cssmPerror("SecTrustCopyAnchorCertificates", ortn); 299 goto errOut; 300 } 301 newAnchors = CFArrayCreateMutableCopy(NULL, 302 CFArrayGetCount(currAnchors) + 1, 303 currAnchors); 304 CFRelease(currAnchors); 305 CFArrayAppendValue(newAnchors, trustedAnchor); 306 ortn = SecTrustSetAnchorCertificates(secTrust, newAnchors); 307 CFRelease(newAnchors); 308 if(ortn) { 309 cssmPerror("SecTrustSetAnchorCertificates", ortn); 310 goto errOut; 311 } 312 } 313 /* evaluate: GO */ 314 ortn = SecTrustEvaluate(secTrust, &secTrustResult); 315 if(ortn) { 316 cssmPerror("SecTrustEvaluate", ortn); 317 goto errOut; 318 } 319 switch(secTrustResult) { 320 case kSecTrustResultUnspecified: 321 /* cert chain valid, no special UserTrust assignments */ 322 case kSecTrustResultProceed: 323 /* cert chain valid AND user explicitly trusts this */ 324 break; 325 default: 326 /* 327 * Cert chain construction failed. 328 * Just go with the single subject cert we were given. 329 */ 330 printf("***Warning: could not construct completed cert chain\n"); 331 ortn = noErr; 332 goto errOut; 333 } 334 335 /* get resulting constructed cert chain */ 336 ortn = SecTrustGetResult(secTrust, &secTrustResult, &certChain, &dummyEv); 337 if(ortn) { 338 cssmPerror("SecTrustEvaluate", ortn); 339 goto errOut; 340 } 341 342 /* 343 * Copy certs from constructed chain to our result array, skipping 344 * the leaf (which is already there, as a SecIdentityRef) and possibly 345 * a root. 346 */ 347 numResCerts = CFArrayGetCount(certChain); 348 if(numResCerts < 2) { 349 /* 350 * Can't happen: if subject was a root, we'd already have returned. 351 * If chain doesn't verify to a root, we'd have bailed after 352 * SecTrustEvaluate(). 353 */ 354 printf("***sslCompleteCertChain screwup: numResCerts %d\n", 355 (int)numResCerts); 356 ortn = noErr; 357 goto errOut; 358 } 359 if(!includeRoot) { 360 /* skip the last (root) cert) */ 361 numResCerts--; 362 } 363 for(CFIndex dex=1; dex<numResCerts; dex++) { 364 certRef = (SecCertificateRef)CFArrayGetValueAtIndex(certChain, dex); 365 CFArrayAppendValue(certArray, certRef); 366 } 367errOut: 368 /* clean up */ 369 if(secTrust) { 370 CFRelease(secTrust); 371 } 372 if(subjCerts) { 373 CFRelease(subjCerts); 374 } 375 if(policy) { 376 CFRelease(policy); 377 } 378 if(policySearch) { 379 CFRelease(policySearch); 380 } 381 *outArray = certArray; 382 return ortn; 383} 384 385 386/* 387 * Given an array of SecIdentityRefs: 388 * -- display a printable name of each identity's cert; 389 * -- prompt user to select which one to use; 390 * 391 * Returns CFIndex of desired identity. A return of <0 indicates 392 * "none - abort". 393 */ 394static CFIndex pickIdent( 395 CFArrayRef idArray) 396{ 397 CFIndex count = CFArrayGetCount(idArray); 398 CFIndex dex; 399 OSStatus ortn; 400 401 if(count == 0) { 402 printf("***sslIdentPicker screwup: no identities found\n"); 403 return -1; 404 } 405 for(dex=0; dex<count; dex++) { 406 SecIdentityRef idRef = (SecIdentityRef)CFArrayGetValueAtIndex(idArray, dex); 407 SecCertificateRef certRef; 408 ortn = SecIdentityCopyCertificate(idRef, &certRef); 409 if(ortn) { 410 /* should never happen */ 411 cssmPerror("SecIdentityCopyCertificate", ortn); 412 return -1; 413 } 414 415 /* get printable name of cert and the keychain it's in */ 416 char *certLabel = kcItemPrintableName((SecKeychainItemRef)certRef); 417 SecKeychainRef kcRef; 418 char *kcLabel; 419 ortn = SecKeychainItemCopyKeychain((SecKeychainItemRef)certRef, &kcRef); 420 if(ortn) { 421 cssmPerror("SecKeychainItemCopyKeychain", ortn); 422 kcLabel = "Unnamed keychain"; 423 } 424 else { 425 kcLabel = kcFileName(kcRef); 426 } 427 printf("[%d] keychain : %s\n", (int)dex, kcLabel); 428 printf(" cert : %s\n", certLabel); 429 free(certLabel); 430 if(ortn == noErr) { 431 free(kcLabel); 432 } 433 CFRelease(certRef); 434 } 435 436 while(1) { 437 fpurge(stdin); 438 printf("\nEnter Certificate number or CR to quit : "); 439 fflush(stdout); 440 char resp[64]; 441 getString(resp, sizeof(resp)); 442 if(resp[0] == '\0') { 443 return -1; 444 } 445 int ires = atoi(resp); 446 if((ires >= 0) && (ires < count)) { 447 return (CFIndex)ires; 448 } 449 printf("***Invalid entry. Type a number between 0 and %d\n", 450 (int)(count-1)); 451 } 452 return -1; 453} 454 455OSStatus simpleIdentPicker( 456 SecKeychainRef kcRef, // NULL means use default list 457 SecIdentityRef *ident) // RETURNED 458{ 459 OSStatus ortn; 460 CFMutableArrayRef idArray = NULL; // holds all SecIdentityRefs found 461 462 /* Search for all identities */ 463 *ident = NULL; 464 SecIdentitySearchRef srchRef = nil; 465 ortn = SecIdentitySearchCreate(kcRef, 466 0, // keyUsage - any 467 &srchRef); 468 if(ortn) { 469 cssmPerror("SecIdentitySearchCreate", (CSSM_RETURN)ortn); 470 printf("Cannot find signing key in keychain.\n"); 471 return ortn; 472 } 473 474 /* get all identities, stuff them into idArray */ 475 SecIdentityRef identity = nil; 476 idArray = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); 477 do { 478 ortn = SecIdentitySearchCopyNext(srchRef, &identity); 479 if(ortn != noErr) { 480 break; 481 } 482 CFArrayAppendValue(idArray, identity); 483 484 /* the array has the retain count we need */ 485 CFRelease(identity); 486 } while(ortn == noErr); 487 488 switch(ortn) { 489 case errSecItemNotFound: 490 if(CFArrayGetCount(idArray) == 0) { 491 printf("No signing keys found in keychain.\n"); 492 return errSecItemNotFound; 493 } 494 else { 495 /* found at least one; proceed */ 496 break; 497 } 498 default: 499 cssmPerror("SecIdentitySearchCopyNext", (CSSM_RETURN)ortn); 500 printf("Cannot find signing key in keychain.\n"); 501 return ortn; 502 } 503 504 /* 505 * If there is just one, use it without asking 506 */ 507 CFIndex whichId; 508 if(CFArrayGetCount(idArray) == 1) { 509 whichId = 0; 510 } 511 else { 512 whichId = pickIdent(idArray); 513 if(whichId < 0) { 514 return CSSMERR_CSSM_USER_CANCELED; 515 } 516 } 517 518 /* keep this one, free the rest */ 519 identity = (SecIdentityRef)CFArrayGetValueAtIndex(idArray, whichId); 520 CFRetain(identity); 521 CFRelease(idArray); 522 *ident = identity; 523 return noErr; 524} 525 526OSStatus identPicker( 527 SecKeychainRef kcRef, // NULL means use default list 528 SecCertificateRef trustedAnchor, // optional additional trusted anchor 529 bool includeRoot, // true --> root is appended to outArray 530 // false --> root not included 531 CFArrayRef *outArray) // created and RETURNED 532{ 533 OSStatus ortn; 534 SecIdentityRef identity; 535 536 ortn = simpleIdentPicker(kcRef, &identity); 537 if(ortn) { 538 return ortn; 539 } 540 return completeCertChain(identity, trustedAnchor, includeRoot, outArray); 541} 542 543