2 * Copyright (c) 2004 Apple Computer, Inc. All Rights Reserved.
4 * @APPLE_LICENSE_HEADER_START@
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
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.
21 * @APPLE_LICENSE_HEADER_END@
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().
31 #include "identPicker.h"
32 #include <sys/param.h>
39 * -- guaranteed no buffer overflow
40 * -- guaranteed NULL-terminated string
41 * -- handles empty string (i.e., response is just CR) properly
43 static void getString(
51 for(dex
=0; dex
<bufSize
-1; dex
++) {
69 * Obtain the printable name of a SecKeychainItemRef as a C string.
70 * Caller must free() the result.
72 static char *kcItemPrintableName(
73 SecKeychainItemRef certRef
)
77 /* just search for the one attr we want */
78 UInt32 tag
= kSecLabelItemAttr
;
79 SecKeychainAttributeInfo attrInfo
;
82 attrInfo
.format
= NULL
;
83 SecKeychainAttributeList
*attrList
= NULL
;
84 SecKeychainAttribute
*attr
= NULL
;
86 OSStatus ortn
= SecKeychainItemCopyAttributesAndData(
87 (SecKeychainItemRef
)certRef
,
91 NULL
, // length - don't need the data
94 cssmPerror("SecKeychainItemCopyAttributesAndData", ortn
);
95 /* may want to be a bit more robust here, but this should
97 return strdup("Unnamed KeychainItem");
99 /* subsequent errors to errOut: */
101 if((attrList
== NULL
) || (attrList
->count
!= 1)) {
102 printf("***Unexpected result fetching label attr\n");
103 crtn
= strdup("Unnamed KeychainItem");
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';
113 SecKeychainItemFreeAttributesAndData(attrList
, NULL
);
118 * Get the final term of a keychain's path as a C string. Caller must free()
121 static char *kcFileName(
122 SecKeychainRef kcRef
)
124 char fullPath
[MAXPATHLEN
+ 1];
126 UInt32 pathLen
= MAXPATHLEN
;
128 ortn
= SecKeychainGetPath(kcRef
, &pathLen
, fullPath
);
130 cssmPerror("SecKeychainGetPath", ortn
);
131 return strdup("orphan keychain");
134 /* NULL terminate the path string and search for final '/' */
135 fullPath
[pathLen
] = '\0';
136 char *lastSlash
= NULL
;
137 char *thisSlash
= fullPath
;
139 thisSlash
= strchr(thisSlash
, '/');
140 if(thisSlash
== NULL
) {
145 lastSlash
= thisSlash
;
146 } while(thisSlash
!= NULL
);
147 if(lastSlash
== NULL
) {
148 /* no slashes, odd, but handle it */
149 return strdup(fullPath
);
152 return strdup(lastSlash
);
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.
161 * Returns true if the cert appears to be a root.
163 static bool isCertRefRoot(
164 SecCertificateRef certRef
)
166 /* just search for the two attrs we want */
167 UInt32 tags
[2] = {kSecSubjectItemAttr
, kSecIssuerItemAttr
};
168 SecKeychainAttributeInfo attrInfo
;
171 attrInfo
.format
= NULL
;
172 SecKeychainAttributeList
*attrList
= NULL
;
173 SecKeychainAttribute
*attr1
= NULL
;
174 SecKeychainAttribute
*attr2
= NULL
;
177 OSStatus ortn
= SecKeychainItemCopyAttributesAndData(
178 (SecKeychainItemRef
)certRef
,
182 NULL
, // length - don't need the data
185 cssmPerror("SecKeychainItemCopyAttributesAndData", ortn
);
186 /* may want to be a bit more robust here, but this should
190 /* subsequent errors to errOut: */
192 if((attrList
== NULL
) || (attrList
->count
!= 2)) {
193 printf("***Unexpected result fetching label attr\n");
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) {
206 SecKeychainItemFreeAttributesAndData(attrList
, NULL
);
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().
216 static 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
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
231 certArray
= CFArrayCreateMutable(NULL
, 0, &kCFTypeArrayCallBacks
);
232 CFArrayAppendValue(certArray
, identity
);
235 * Case 1: identity is a root; we're done. Note that this case
236 * overrides the includeRoot argument.
238 SecCertificateRef certRef
;
239 OSStatus ortn
= SecIdentityCopyCertificate(identity
, &certRef
);
241 /* should never happen */
242 cssmPerror("SecIdentityCopyCertificate", ortn
);
245 bool isRoot
= isCertRefRoot(certRef
);
247 *outArray
= certArray
;
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.)
258 CFMutableArrayRef subjCerts
= CFArrayCreateMutable(NULL
, 1, &kCFTypeArrayCallBacks
);
259 CFArraySetValueAtIndex(subjCerts
, 0, certRef
);
261 /* the array owns the subject cert ref now */
264 /* Get a SecPolicyRef for generic X509 cert chain verification */
265 ortn
= SecPolicySearchCreate(CSSM_CERT_X_509v3
,
266 &CSSMOID_APPLE_X509_BASIC
,
270 cssmPerror("SecPolicySearchCreate", ortn
);
273 ortn
= SecPolicySearchCopyNext(policySearch
, &policy
);
275 cssmPerror("SecPolicySearchCopyNext", ortn
);
279 /* build a SecTrustRef for specified policy and certs */
280 ortn
= SecTrustCreateWithCertificates(subjCerts
,
283 cssmPerror("SecTrustCreateWithCertificates", ortn
);
289 * Tell SecTrust to trust this one in addition to the current
290 * trusted system-wide anchors.
292 CFMutableArrayRef newAnchors
;
293 CFArrayRef currAnchors
;
295 ortn
= SecTrustCopyAnchorCertificates(&currAnchors
);
297 /* should never happen */
298 cssmPerror("SecTrustCopyAnchorCertificates", ortn
);
301 newAnchors
= CFArrayCreateMutableCopy(NULL
,
302 CFArrayGetCount(currAnchors
) + 1,
304 CFRelease(currAnchors
);
305 CFArrayAppendValue(newAnchors
, trustedAnchor
);
306 ortn
= SecTrustSetAnchorCertificates(secTrust
, newAnchors
);
307 CFRelease(newAnchors
);
309 cssmPerror("SecTrustSetAnchorCertificates", ortn
);
314 ortn
= SecTrustEvaluate(secTrust
, &secTrustResult
);
316 cssmPerror("SecTrustEvaluate", ortn
);
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 */
327 * Cert chain construction failed.
328 * Just go with the single subject cert we were given.
330 printf("***Warning: could not construct completed cert chain\n");
335 /* get resulting constructed cert chain */
336 ortn
= SecTrustGetResult(secTrust
, &secTrustResult
, &certChain
, &dummyEv
);
338 cssmPerror("SecTrustEvaluate", ortn
);
343 * Copy certs from constructed chain to our result array, skipping
344 * the leaf (which is already there, as a SecIdentityRef) and possibly
347 numResCerts
= CFArrayGetCount(certChain
);
348 if(numResCerts
< 2) {
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().
354 printf("***sslCompleteCertChain screwup: numResCerts %d\n",
360 /* skip the last (root) cert) */
363 for(CFIndex dex
=1; dex
<numResCerts
; dex
++) {
364 certRef
= (SecCertificateRef
)CFArrayGetValueAtIndex(certChain
, dex
);
365 CFArrayAppendValue(certArray
, certRef
);
373 CFRelease(subjCerts
);
379 CFRelease(policySearch
);
381 *outArray
= certArray
;
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;
391 * Returns CFIndex of desired identity. A return of <0 indicates
394 static CFIndex
pickIdent(
397 CFIndex count
= CFArrayGetCount(idArray
);
402 printf("***sslIdentPicker screwup: no identities found\n");
405 for(dex
=0; dex
<count
; dex
++) {
406 SecIdentityRef idRef
= (SecIdentityRef
)CFArrayGetValueAtIndex(idArray
, dex
);
407 SecCertificateRef certRef
;
408 ortn
= SecIdentityCopyCertificate(idRef
, &certRef
);
410 /* should never happen */
411 cssmPerror("SecIdentityCopyCertificate", ortn
);
415 /* get printable name of cert and the keychain it's in */
416 char *certLabel
= kcItemPrintableName((SecKeychainItemRef
)certRef
);
417 SecKeychainRef kcRef
;
419 ortn
= SecKeychainItemCopyKeychain((SecKeychainItemRef
)certRef
, &kcRef
);
421 cssmPerror("SecKeychainItemCopyKeychain", ortn
);
422 kcLabel
= "Unnamed keychain";
425 kcLabel
= kcFileName(kcRef
);
427 printf("[%d] keychain : %s\n", (int)dex
, kcLabel
);
428 printf(" cert : %s\n", certLabel
);
438 printf("\nEnter Certificate number or CR to quit : ");
441 getString(resp
, sizeof(resp
));
442 if(resp
[0] == '\0') {
445 int ires
= atoi(resp
);
446 if((ires
>= 0) && (ires
< count
)) {
447 return (CFIndex
)ires
;
449 printf("***Invalid entry. Type a number between 0 and %d\n",
455 OSStatus
simpleIdentPicker(
456 SecKeychainRef kcRef
, // NULL means use default list
457 SecIdentityRef
*ident
) // RETURNED
460 CFMutableArrayRef idArray
= NULL
; // holds all SecIdentityRefs found
462 /* Search for all identities */
464 SecIdentitySearchRef srchRef
= nil
;
465 ortn
= SecIdentitySearchCreate(kcRef
,
469 cssmPerror("SecIdentitySearchCreate", (CSSM_RETURN
)ortn
);
470 printf("Cannot find signing key in keychain.\n");
474 /* get all identities, stuff them into idArray */
475 SecIdentityRef identity
= nil
;
476 idArray
= CFArrayCreateMutable(NULL
, 0, &kCFTypeArrayCallBacks
);
478 ortn
= SecIdentitySearchCopyNext(srchRef
, &identity
);
482 CFArrayAppendValue(idArray
, identity
);
484 /* the array has the retain count we need */
486 } while(ortn
== noErr
);
489 case errSecItemNotFound
:
490 if(CFArrayGetCount(idArray
) == 0) {
491 printf("No signing keys found in keychain.\n");
492 return errSecItemNotFound
;
495 /* found at least one; proceed */
499 cssmPerror("SecIdentitySearchCopyNext", (CSSM_RETURN
)ortn
);
500 printf("Cannot find signing key in keychain.\n");
505 * If there is just one, use it without asking
508 if(CFArrayGetCount(idArray
) == 1) {
512 whichId
= pickIdent(idArray
);
514 return CSSMERR_CSSM_USER_CANCELED
;
518 /* keep this one, free the rest */
519 identity
= (SecIdentityRef
)CFArrayGetValueAtIndex(idArray
, whichId
);
526 OSStatus
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
534 SecIdentityRef identity
;
536 ortn
= simpleIdentPicker(kcRef
, &identity
);
540 return completeCertChain(identity
, trustedAnchor
, includeRoot
, outArray
);