--- /dev/null
+/*
+ * Copyright (c) 2004 Apple Computer, Inc. All Rights Reserved.
+ *
+ * @APPLE_LICENSE_HEADER_START@
+ *
+ * This file contains Original Code and/or Modifications of Original Code
+ * as defined in and that are subject to the Apple Public Source License
+ * Version 2.0 (the 'License'). You may not use this file except in
+ * compliance with the License. Please obtain a copy of the License at
+ * http://www.opensource.apple.com/apsl/ and read it before using this
+ * file.
+ *
+ * The Original Code and all software distributed under the License are
+ * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
+ * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
+ * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
+ * Please see the License for the specific language governing rights and
+ * limitations under the License.
+ *
+ * @APPLE_LICENSE_HEADER_END@
+ */
+/*
+ * identPicker.cpp - Given a keychain, select from possible multiple
+ * SecIdentityRefs via stdio UI, and cook up a
+ * CFArray containing that identity and all certs needed
+ * for cert verification by an SSL peer. The resulting
+ * CFArrayRef is suitable for passing to SSLSetCertificate().
+ */
+
+#include "identPicker.h"
+#include <sys/param.h>
+#include <string.h>
+#include <stdio.h>
+#include <ctype.h>
+
+/*
+ * Safe gets().
+ * -- guaranteed no buffer overflow
+ * -- guaranteed NULL-terminated string
+ * -- handles empty string (i.e., response is just CR) properly
+ */
+static void getString(
+ char *buf,
+ unsigned bufSize)
+{
+ unsigned dex;
+ char c;
+ char *cp = buf;
+
+ for(dex=0; dex<bufSize-1; dex++) {
+ c = getchar();
+ if(!isprint(c)) {
+ break;
+ }
+ switch(c) {
+ case '\n':
+ case '\r':
+ goto done;
+ default:
+ *cp++ = c;
+ }
+ }
+done:
+ *cp = '\0';
+}
+
+/*
+ * Obtain the printable name of a SecKeychainItemRef as a C string.
+ * Caller must free() the result.
+ */
+static char *kcItemPrintableName(
+ SecKeychainItemRef certRef)
+{
+ char *crtn = NULL;
+
+ /* just search for the one attr we want */
+ UInt32 tag = kSecLabelItemAttr;
+ SecKeychainAttributeInfo attrInfo;
+ attrInfo.count = 1;
+ attrInfo.tag = &tag;
+ attrInfo.format = NULL;
+ SecKeychainAttributeList *attrList = NULL;
+ SecKeychainAttribute *attr = NULL;
+
+ OSStatus ortn = SecKeychainItemCopyAttributesAndData(
+ (SecKeychainItemRef)certRef,
+ &attrInfo,
+ NULL, // itemClass
+ &attrList,
+ NULL, // length - don't need the data
+ NULL); // outData
+ if(ortn) {
+ cssmPerror("SecKeychainItemCopyAttributesAndData", ortn);
+ /* may want to be a bit more robust here, but this should
+ * never happen */
+ return strdup("Unnamed KeychainItem");
+ }
+ /* subsequent errors to errOut: */
+
+ if((attrList == NULL) || (attrList->count != 1)) {
+ printf("***Unexpected result fetching label attr\n");
+ crtn = strdup("Unnamed KeychainItem");
+ goto errOut;
+ }
+ /* We're assuming 8-bit ASCII attribute data here... */
+ attr = attrList->attr;
+ crtn = (char *)malloc(attr->length + 1);
+ memmove(crtn, attr->data, attr->length);
+ crtn[attr->length] = '\0';
+
+errOut:
+ SecKeychainItemFreeAttributesAndData(attrList, NULL);
+ return crtn;
+}
+
+/*
+ * Get the final term of a keychain's path as a C string. Caller must free()
+ * the result.
+ */
+static char *kcFileName(
+ SecKeychainRef kcRef)
+{
+ char fullPath[MAXPATHLEN + 1];
+ OSStatus ortn;
+ UInt32 pathLen = MAXPATHLEN;
+
+ ortn = SecKeychainGetPath(kcRef, &pathLen, fullPath);
+ if(ortn) {
+ cssmPerror("SecKeychainGetPath", ortn);
+ return strdup("orphan keychain");
+ }
+
+ /* NULL terminate the path string and search for final '/' */
+ fullPath[pathLen] = '\0';
+ char *lastSlash = NULL;
+ char *thisSlash = fullPath;
+ do {
+ thisSlash = strchr(thisSlash, '/');
+ if(thisSlash == NULL) {
+ /* done */
+ break;
+ }
+ thisSlash++;
+ lastSlash = thisSlash;
+ } while(thisSlash != NULL);
+ if(lastSlash == NULL) {
+ /* no slashes, odd, but handle it */
+ return strdup(fullPath);
+ }
+ else {
+ return strdup(lastSlash);
+ }
+}
+
+/*
+ * Determine if specified SecCertificateRef is a self-signed cert.
+ * We do this by comparing the subject and issuerr names; no cryptographic
+ * verification is performed.
+ *
+ * Returns true if the cert appears to be a root.
+ */
+static bool isCertRefRoot(
+ SecCertificateRef certRef)
+{
+ /* just search for the two attrs we want */
+ UInt32 tags[2] = {kSecSubjectItemAttr, kSecIssuerItemAttr};
+ SecKeychainAttributeInfo attrInfo;
+ attrInfo.count = 2;
+ attrInfo.tag = tags;
+ attrInfo.format = NULL;
+ SecKeychainAttributeList *attrList = NULL;
+ SecKeychainAttribute *attr1 = NULL;
+ SecKeychainAttribute *attr2 = NULL;
+ bool brtn = false;
+
+ OSStatus ortn = SecKeychainItemCopyAttributesAndData(
+ (SecKeychainItemRef)certRef,
+ &attrInfo,
+ NULL, // itemClass
+ &attrList,
+ NULL, // length - don't need the data
+ NULL); // outData
+ if(ortn) {
+ cssmPerror("SecKeychainItemCopyAttributesAndData", ortn);
+ /* may want to be a bit more robust here, but this should
+ * never happen */
+ return false;
+ }
+ /* subsequent errors to errOut: */
+
+ if((attrList == NULL) || (attrList->count != 2)) {
+ printf("***Unexpected result fetching label attr\n");
+ goto errOut;
+ }
+
+ /* rootness is just byte-for-byte compare of the two names */
+ attr1 = &attrList->attr[0];
+ attr2 = &attrList->attr[1];
+ if(attr1->length == attr2->length) {
+ if(memcmp(attr1->data, attr2->data, attr1->length) == 0) {
+ brtn = true;
+ }
+ }
+errOut:
+ SecKeychainItemFreeAttributesAndData(attrList, NULL);
+ return brtn;
+}
+
+
+/*
+ * Given a SecIdentityRef, do our best to construct a complete, ordered, and
+ * verified cert chain, returning the result in a CFArrayRef. The result is
+ * suitable for use when calling SSLSetCertificate().
+ */
+static OSStatus completeCertChain(
+ SecIdentityRef identity,
+ SecCertificateRef trustedAnchor, // optional additional trusted anchor
+ bool includeRoot, // include the root in outArray
+ CFArrayRef *outArray) // created and RETURNED
+{
+ CFMutableArrayRef certArray;
+ SecTrustRef secTrust = NULL;
+ SecPolicyRef policy = NULL;
+ SecPolicySearchRef policySearch = NULL;
+ SecTrustResultType secTrustResult;
+ CSSM_TP_APPLE_EVIDENCE_INFO *dummyEv; // not used
+ CFArrayRef certChain = NULL; // constructed chain
+ CFIndex numResCerts;
+
+ certArray = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
+ CFArrayAppendValue(certArray, identity);
+
+ /*
+ * Case 1: identity is a root; we're done. Note that this case
+ * overrides the includeRoot argument.
+ */
+ SecCertificateRef certRef;
+ OSStatus ortn = SecIdentityCopyCertificate(identity, &certRef);
+ if(ortn) {
+ /* should never happen */
+ cssmPerror("SecIdentityCopyCertificate", ortn);
+ return ortn;
+ }
+ bool isRoot = isCertRefRoot(certRef);
+ if(isRoot) {
+ *outArray = certArray;
+ CFRelease(certRef);
+ return noErr;
+ }
+
+ /*
+ * Now use SecTrust to get a complete cert chain, using all of the
+ * user's keychains to look for intermediate certs.
+ * NOTE this does NOT handle root certs which are not in the system
+ * root cert DB. (The above case, where the identity is a root cert, does.)
+ */
+ CFMutableArrayRef subjCerts = CFArrayCreateMutable(NULL, 1, &kCFTypeArrayCallBacks);
+ CFArraySetValueAtIndex(subjCerts, 0, certRef);
+
+ /* the array owns the subject cert ref now */
+ CFRelease(certRef);
+
+ /* Get a SecPolicyRef for generic X509 cert chain verification */
+ ortn = SecPolicySearchCreate(CSSM_CERT_X_509v3,
+ &CSSMOID_APPLE_X509_BASIC,
+ NULL, // value
+ &policySearch);
+ if(ortn) {
+ cssmPerror("SecPolicySearchCreate", ortn);
+ goto errOut;
+ }
+ ortn = SecPolicySearchCopyNext(policySearch, &policy);
+ if(ortn) {
+ cssmPerror("SecPolicySearchCopyNext", ortn);
+ goto errOut;
+ }
+
+ /* build a SecTrustRef for specified policy and certs */
+ ortn = SecTrustCreateWithCertificates(subjCerts,
+ policy, &secTrust);
+ if(ortn) {
+ cssmPerror("SecTrustCreateWithCertificates", ortn);
+ goto errOut;
+ }
+
+ if(trustedAnchor) {
+ /*
+ * Tell SecTrust to trust this one in addition to the current
+ * trusted system-wide anchors.
+ */
+ CFMutableArrayRef newAnchors;
+ CFArrayRef currAnchors;
+
+ ortn = SecTrustCopyAnchorCertificates(&currAnchors);
+ if(ortn) {
+ /* should never happen */
+ cssmPerror("SecTrustCopyAnchorCertificates", ortn);
+ goto errOut;
+ }
+ newAnchors = CFArrayCreateMutableCopy(NULL,
+ CFArrayGetCount(currAnchors) + 1,
+ currAnchors);
+ CFRelease(currAnchors);
+ CFArrayAppendValue(newAnchors, trustedAnchor);
+ ortn = SecTrustSetAnchorCertificates(secTrust, newAnchors);
+ CFRelease(newAnchors);
+ if(ortn) {
+ cssmPerror("SecTrustSetAnchorCertificates", ortn);
+ goto errOut;
+ }
+ }
+ /* evaluate: GO */
+ ortn = SecTrustEvaluate(secTrust, &secTrustResult);
+ if(ortn) {
+ cssmPerror("SecTrustEvaluate", ortn);
+ goto errOut;
+ }
+ switch(secTrustResult) {
+ case kSecTrustResultUnspecified:
+ /* cert chain valid, no special UserTrust assignments */
+ case kSecTrustResultProceed:
+ /* cert chain valid AND user explicitly trusts this */
+ break;
+ default:
+ /*
+ * Cert chain construction failed.
+ * Just go with the single subject cert we were given.
+ */
+ printf("***Warning: could not construct completed cert chain\n");
+ ortn = noErr;
+ goto errOut;
+ }
+
+ /* get resulting constructed cert chain */
+ ortn = SecTrustGetResult(secTrust, &secTrustResult, &certChain, &dummyEv);
+ if(ortn) {
+ cssmPerror("SecTrustEvaluate", ortn);
+ goto errOut;
+ }
+
+ /*
+ * Copy certs from constructed chain to our result array, skipping
+ * the leaf (which is already there, as a SecIdentityRef) and possibly
+ * a root.
+ */
+ numResCerts = CFArrayGetCount(certChain);
+ if(numResCerts < 2) {
+ /*
+ * Can't happen: if subject was a root, we'd already have returned.
+ * If chain doesn't verify to a root, we'd have bailed after
+ * SecTrustEvaluate().
+ */
+ printf("***sslCompleteCertChain screwup: numResCerts %d\n",
+ (int)numResCerts);
+ ortn = noErr;
+ goto errOut;
+ }
+ if(!includeRoot) {
+ /* skip the last (root) cert) */
+ numResCerts--;
+ }
+ for(CFIndex dex=1; dex<numResCerts; dex++) {
+ certRef = (SecCertificateRef)CFArrayGetValueAtIndex(certChain, dex);
+ CFArrayAppendValue(certArray, certRef);
+ }
+errOut:
+ /* clean up */
+ if(secTrust) {
+ CFRelease(secTrust);
+ }
+ if(subjCerts) {
+ CFRelease(subjCerts);
+ }
+ if(policy) {
+ CFRelease(policy);
+ }
+ if(policySearch) {
+ CFRelease(policySearch);
+ }
+ *outArray = certArray;
+ return ortn;
+}
+
+
+/*
+ * Given an array of SecIdentityRefs:
+ * -- display a printable name of each identity's cert;
+ * -- prompt user to select which one to use;
+ *
+ * Returns CFIndex of desired identity. A return of <0 indicates
+ * "none - abort".
+ */
+static CFIndex pickIdent(
+ CFArrayRef idArray)
+{
+ CFIndex count = CFArrayGetCount(idArray);
+ CFIndex dex;
+ OSStatus ortn;
+
+ if(count == 0) {
+ printf("***sslIdentPicker screwup: no identities found\n");
+ return -1;
+ }
+ for(dex=0; dex<count; dex++) {
+ SecIdentityRef idRef = (SecIdentityRef)CFArrayGetValueAtIndex(idArray, dex);
+ SecCertificateRef certRef;
+ ortn = SecIdentityCopyCertificate(idRef, &certRef);
+ if(ortn) {
+ /* should never happen */
+ cssmPerror("SecIdentityCopyCertificate", ortn);
+ return -1;
+ }
+
+ /* get printable name of cert and the keychain it's in */
+ char *certLabel = kcItemPrintableName((SecKeychainItemRef)certRef);
+ SecKeychainRef kcRef;
+ char *kcLabel;
+ ortn = SecKeychainItemCopyKeychain((SecKeychainItemRef)certRef, &kcRef);
+ if(ortn) {
+ cssmPerror("SecKeychainItemCopyKeychain", ortn);
+ kcLabel = "Unnamed keychain";
+ }
+ else {
+ kcLabel = kcFileName(kcRef);
+ }
+ printf("[%d] keychain : %s\n", (int)dex, kcLabel);
+ printf(" cert : %s\n", certLabel);
+ free(certLabel);
+ if(ortn == noErr) {
+ free(kcLabel);
+ }
+ CFRelease(certRef);
+ }
+
+ while(1) {
+ fpurge(stdin);
+ printf("\nEnter Certificate number or CR to quit : ");
+ fflush(stdout);
+ char resp[64];
+ getString(resp, sizeof(resp));
+ if(resp[0] == '\0') {
+ return -1;
+ }
+ int ires = atoi(resp);
+ if((ires >= 0) && (ires < count)) {
+ return (CFIndex)ires;
+ }
+ printf("***Invalid entry. Type a number between 0 and %d\n",
+ (int)(count-1));
+ }
+ return -1;
+}
+
+OSStatus simpleIdentPicker(
+ SecKeychainRef kcRef, // NULL means use default list
+ SecIdentityRef *ident) // RETURNED
+{
+ OSStatus ortn;
+ CFMutableArrayRef idArray = NULL; // holds all SecIdentityRefs found
+
+ /* Search for all identities */
+ *ident = NULL;
+ SecIdentitySearchRef srchRef = nil;
+ ortn = SecIdentitySearchCreate(kcRef,
+ 0, // keyUsage - any
+ &srchRef);
+ if(ortn) {
+ cssmPerror("SecIdentitySearchCreate", (CSSM_RETURN)ortn);
+ printf("Cannot find signing key in keychain.\n");
+ return ortn;
+ }
+
+ /* get all identities, stuff them into idArray */
+ SecIdentityRef identity = nil;
+ idArray = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
+ do {
+ ortn = SecIdentitySearchCopyNext(srchRef, &identity);
+ if(ortn != noErr) {
+ break;
+ }
+ CFArrayAppendValue(idArray, identity);
+
+ /* the array has the retain count we need */
+ CFRelease(identity);
+ } while(ortn == noErr);
+
+ switch(ortn) {
+ case errSecItemNotFound:
+ if(CFArrayGetCount(idArray) == 0) {
+ printf("No signing keys found in keychain.\n");
+ return errSecItemNotFound;
+ }
+ else {
+ /* found at least one; proceed */
+ break;
+ }
+ default:
+ cssmPerror("SecIdentitySearchCopyNext", (CSSM_RETURN)ortn);
+ printf("Cannot find signing key in keychain.\n");
+ return ortn;
+ }
+
+ /*
+ * If there is just one, use it without asking
+ */
+ CFIndex whichId;
+ if(CFArrayGetCount(idArray) == 1) {
+ whichId = 0;
+ }
+ else {
+ whichId = pickIdent(idArray);
+ if(whichId < 0) {
+ return CSSMERR_CSSM_USER_CANCELED;
+ }
+ }
+
+ /* keep this one, free the rest */
+ identity = (SecIdentityRef)CFArrayGetValueAtIndex(idArray, whichId);
+ CFRetain(identity);
+ CFRelease(idArray);
+ *ident = identity;
+ return noErr;
+}
+
+OSStatus identPicker(
+ SecKeychainRef kcRef, // NULL means use default list
+ SecCertificateRef trustedAnchor, // optional additional trusted anchor
+ bool includeRoot, // true --> root is appended to outArray
+ // false --> root not included
+ CFArrayRef *outArray) // created and RETURNED
+{
+ OSStatus ortn;
+ SecIdentityRef identity;
+
+ ortn = simpleIdentPicker(kcRef, &identity);
+ if(ortn) {
+ return ortn;
+ }
+ return completeCertChain(identity, trustedAnchor, includeRoot, outArray);
+}
+