/* * Copyright (c) 2003-2010 Apple 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@ * * identity_find.c */ #include "identity_find.h" #include "keychain_utilities.h" #include "trusted_cert_utils.h" #include "security.h" #include #include #include #include #include #include #include #include #include #include #include // cssmErrorString #include // SecCertificateInferLabel, SecDigestGetData #include // SecIdentitySearchCreateWithPolicy #include SecIdentityRef find_identity(CFTypeRef keychainOrArray, const char *identity, const char *hash, CSSM_KEYUSE keyUsage) { SecIdentityRef identityRef = NULL; SecIdentitySearchRef searchRef = NULL; OSStatus status = SecIdentitySearchCreate(keychainOrArray, keyUsage, &searchRef); if (status) { return identityRef; } // check input hash string and convert to data CSSM_DATA hashData = { 0, NULL }; if (hash) { CSSM_SIZE len = strlen(hash)/2; hashData.Length = len; hashData.Data = (uint8 *)malloc(hashData.Length); fromHex(hash, &hashData); } // filter candidates against the hash (or the name, if no hash provided) CFStringRef matchRef = (identity) ? CFStringCreateWithCString(NULL, identity, kCFStringEncodingUTF8) : NULL; Boolean exactMatch = FALSE; CSSM_DATA certData = { 0, NULL }; SecIdentityRef candidate = NULL; while (SecIdentitySearchCopyNext(searchRef, &candidate) == noErr) { SecCertificateRef cert = NULL; if (SecIdentityCopyCertificate(candidate, &cert) != noErr) { safe_CFRelease(&candidate); continue; } if (SecCertificateGetData(cert, &certData) != noErr) { safe_CFRelease(&cert); safe_CFRelease(&candidate); continue; } if (hash) { uint8 candidate_sha1_hash[20]; CSSM_DATA digest; digest.Length = sizeof(candidate_sha1_hash); digest.Data = candidate_sha1_hash; if ((SecDigestGetData(CSSM_ALGID_SHA1, &digest, &certData) == CSSM_OK) && (hashData.Length == digest.Length) && (!memcmp(hashData.Data, digest.Data, digest.Length))) { exactMatch = TRUE; identityRef = candidate; // currently retained safe_CFRelease(&cert); break; // we're done - can't get more exact than this } } else { // copy certificate name CFStringRef nameRef = NULL; if (SecCertificateCopyCommonName(cert, &nameRef) != noErr) { safe_CFRelease(&cert); safe_CFRelease(&candidate); continue; // no name, so no match is possible } CFIndex nameLen = CFStringGetLength(nameRef); CFIndex bufLen = 1 + CFStringGetMaximumSizeForEncoding(nameLen, kCFStringEncodingUTF8); char *nameBuf = (char *)malloc(bufLen); if (!CFStringGetCString(nameRef, nameBuf, bufLen-1, kCFStringEncodingUTF8)) nameBuf[0]=0; if (!strcmp(identity, "*")) { // special case: means "just take the first one" sec_error("Using identity \"%s\"", nameBuf); identityRef = candidate; // currently retained free(nameBuf); safe_CFRelease(&nameRef); safe_CFRelease(&cert); break; } CFRange find = { kCFNotFound, 0 }; if (nameRef && matchRef) find = CFStringFind(nameRef, matchRef, kCFCompareCaseInsensitive | kCFCompareNonliteral); Boolean isExact = (find.location == 0 && find.length == nameLen); if (find.location == kCFNotFound) { free(nameBuf); safe_CFRelease(&nameRef); safe_CFRelease(&cert); safe_CFRelease(&candidate); continue; // no match } if (identityRef) { // got two matches if (exactMatch && !isExact) { // prior is better; ignore this one free(nameBuf); safe_CFRelease(&nameRef); safe_CFRelease(&cert); safe_CFRelease(&candidate); continue; } if (exactMatch == isExact) { // same class of match if (CFEqual(identityRef, candidate)) { // identities have same cert free(nameBuf); safe_CFRelease(&nameRef); safe_CFRelease(&cert); safe_CFRelease(&candidate); continue; } // ambiguity - must fail sec_error("\"%s\" is ambiguous, matches more than one certificate", identity); free(nameBuf); safe_CFRelease(&nameRef); safe_CFRelease(&cert); safe_CFRelease(&candidate); safe_CFRelease(&identityRef); break; } safe_CFRelease(&identityRef); // about to replace with this one } identityRef = candidate; // currently retained exactMatch = isExact; free(nameBuf); safe_CFRelease(&nameRef); } safe_CFRelease(&cert); } safe_CFRelease(&searchRef); safe_CFRelease(&matchRef); if (hashData.Data) { free(hashData.Data); } return identityRef; } void printIdentity(SecIdentityRef identity, SecPolicyRef policy, int ordinalValue) { OSStatus status; Boolean printHash = TRUE, printName = TRUE; SecCertificateRef cert = NULL; status = SecIdentityCopyCertificate(identity, &cert); if (!status) { CSSM_DATA certData = { 0, nil }; (void) SecCertificateGetData(cert, &certData); fprintf(stdout, "%3d) ", ordinalValue); if (printHash) { uint8 sha1_hash[20]; CSSM_DATA digest; digest.Length = sizeof(sha1_hash); digest.Data = sha1_hash; if (SecDigestGetData(CSSM_ALGID_SHA1, &digest, &certData) == CSSM_OK) { unsigned int i; uint32 len = digest.Length; uint8 *cp = digest.Data; for(i=0; i 0) { CFIndex bufLen = 1 + CFStringGetMaximumSizeForEncoding(nameLen, kCFStringEncodingUTF8); nameBuf = (char *)malloc(bufLen); if (!CFStringGetCString(nameRef, nameBuf, bufLen-1, kCFStringEncodingUTF8)) nameBuf[0]=0; } fprintf(stdout, " \"%s\"", (nameBuf && nameBuf[0] != 0) ? nameBuf : ""); if (nameBuf) free(nameBuf); safe_CFRelease(&nameRef); } // Default to X.509 Basic if no policy was specified if (!policy) { SecPolicySearchRef policySearch = NULL; if (SecPolicySearchCreate(CSSM_CERT_X_509v3, &CSSMOID_APPLE_X509_BASIC, NULL, &policySearch)==noErr) { SecPolicySearchCopyNext(policySearch, &policy); } safe_CFRelease(&policySearch); } else { CFRetain(policy); } // Create the trust reference, given policy and certificates SecTrustRef trust = nil; SecTrustResultType trustResult = kSecTrustResultInvalid; OSStatus trustResultCode = noErr; CFMutableArrayRef certificates = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); if (certificates) { CFArrayAppendValue(certificates, cert); } status = SecTrustCreateWithCertificates((CFArrayRef)certificates, policy, &trust); if (!status) { status = SecTrustEvaluate(trust, &trustResult); } if (trustResult != kSecTrustResultInvalid) { status = SecTrustGetCssmResultCode(trust, &trustResultCode); } if (trustResultCode != noErr) { fprintf(stdout, " (%s)\n", cssmErrorString(trustResultCode)); } else { fprintf(stdout, "\n"); } safe_CFRelease(&trust); safe_CFRelease(&policy); safe_CFRelease(&certificates); } safe_CFRelease(&cert); } void do_identity_search_with_policy(CFTypeRef keychainOrArray, const char *name, const CSSM_OID* oidPtr, CSSM_KEYUSE keyUsage, Boolean client, Boolean validOnly) { // set up SMIME options with provided data CE_KeyUsage ceKeyUsage = 0; if (keyUsage & CSSM_KEYUSE_SIGN) ceKeyUsage |= CE_KU_DigitalSignature; if (keyUsage & CSSM_KEYUSE_ENCRYPT) ceKeyUsage |= CE_KU_KeyEncipherment; CSSM_APPLE_TP_SMIME_OPTIONS smimeOpts = { CSSM_APPLE_TP_SMIME_OPTS_VERSION, // Version ceKeyUsage, // IntendedUsage name ? strlen(name) : 0, // SenderEmailLen name // SenderEmail }; CSSM_DATA smimeValue = { sizeof(smimeOpts), (uint8*)&smimeOpts }; // set up SSL options with provided data CSSM_APPLE_TP_SSL_OPTIONS sslOpts = { CSSM_APPLE_TP_SSL_OPTS_VERSION, // Version (name && !client) ? strlen(name) : 0, // ServerNameLen (client) ? NULL : name, // ServerName (client) ? CSSM_APPLE_TP_SSL_CLIENT : 0 // Flags }; CSSM_DATA sslValue = { sizeof(sslOpts), (uint8*)&sslOpts }; // get a policy ref for the specified policy OID OSStatus status = noErr; SecPolicyRef policy = NULL; SecPolicySearchRef policySearch = NULL; status = SecPolicySearchCreate(CSSM_CERT_X_509v3, oidPtr, NULL, &policySearch); if (!status) status = SecPolicySearchCopyNext(policySearch, &policy); CSSM_DATA *policyValue = NULL; const char *policyName = ""; if (compareOids(oidPtr, &CSSMOID_APPLE_TP_SMIME)) { policyName = "S/MIME"; policyValue = &smimeValue; } else if (compareOids(oidPtr, &CSSMOID_APPLE_TP_SSL)) { if (client) policyName = "SSL (client)"; else policyName = "SSL (server)"; policyValue = &sslValue; } else if (compareOids(oidPtr, &CSSMOID_APPLE_TP_EAP)) { policyName = "EAP"; } else if (compareOids(oidPtr, &CSSMOID_APPLE_TP_IP_SEC)) { policyName = "IPsec"; } else if (compareOids(oidPtr, &CSSMOID_APPLE_TP_ICHAT)) { policyName = "iChat"; } else if (compareOids(oidPtr, &CSSMOID_APPLE_TP_CODE_SIGNING)) { policyName = "Code Signing"; } else if (compareOids(oidPtr, &CSSMOID_APPLE_X509_BASIC)) { policyName = "X.509 Basic"; } else if (compareOids(oidPtr, &CSSMOID_APPLE_TP_MACAPPSTORE_RECEIPT)) { policyName = "Mac App Store Receipt"; } // set the policy's value, if there is one (this is specific to certain policies) if (policy && policyValue) status = SecPolicySetValue(policy, policyValue); CFStringRef idStr = (name) ? CFStringCreateWithCStringNoCopy(NULL, name, kCFStringEncodingUTF8, kCFAllocatorNull) : NULL; SecIdentitySearchRef searchRef = NULL; int identityCount = 0; if (!validOnly) { // create an identity search, specifying all identities (i.e. returnOnlyValidIdentities=FALSE) // this should return all identities which match the policy and key usage, regardless of validity fprintf(stdout, "\nPolicy: %s\n", policyName); fprintf(stdout, " Matching identities\n"); status = SecIdentitySearchCreateWithPolicy(policy, idStr, keyUsage, keychainOrArray, FALSE, &searchRef); if (!status) { SecIdentityRef identityRef = NULL; while (SecIdentitySearchCopyNext(searchRef, &identityRef) == noErr) { identityCount++; printIdentity(identityRef, policy, identityCount); safe_CFRelease(&identityRef); } safe_CFRelease(&searchRef); } fprintf(stdout, " %d identities found\n\n", identityCount); } // create a second identity search, specifying only valid identities (i.e. returnOnlyValidIdentities=TRUE) // this should return only valid identities for the policy. identityCount = 0; if (!validOnly) { fprintf(stdout, " Valid identities only\n"); } status = SecIdentitySearchCreateWithPolicy(policy, idStr, keyUsage, keychainOrArray, TRUE, &searchRef); if (!status) { SecIdentityRef identityRef = NULL; while (SecIdentitySearchCopyNext(searchRef, &identityRef) == noErr) { identityCount++; printIdentity(identityRef, policy, identityCount); safe_CFRelease(&identityRef); } safe_CFRelease(&searchRef); } fprintf(stdout, " %d valid identities found\n", identityCount); safe_CFRelease(&idStr); safe_CFRelease(&policy); safe_CFRelease(policySearch); } void do_system_identity_search(CFStringRef domain) { SecIdentityRef identity = NULL; OSStatus status = SecIdentityCopySystemIdentity(domain, &identity, NULL); if (CFEqual(domain, kSecIdentityDomainDefault)) { fprintf(stdout, "\n System default identity\n"); } else if (CFEqual(domain, kSecIdentityDomainKerberosKDC)) { fprintf(stdout, "\n System Kerberos KDC identity\n"); } if (!status && identity) { SecPolicyRef policy = NULL; SecPolicySearchRef policySearch = NULL; if (SecPolicySearchCreate(CSSM_CERT_X_509v3, &CSSMOID_APPLE_X509_BASIC, NULL, &policySearch) == noErr) { if (SecPolicySearchCopyNext(policySearch, &policy) == noErr) { printIdentity(identity, policy, 1); CFRelease(policy); } safe_CFRelease(&policySearch); } safe_CFRelease(&identity); } } int do_find_identities(CFTypeRef keychainOrArray, const char *name, unsigned int policyFlags, Boolean validOnly) { int result = 0; if (name) { fprintf(stdout, "Looking for identities matching \"%s\"\n", name); } if (policyFlags & (1 << 0)) do_identity_search_with_policy(keychainOrArray, name, &CSSMOID_APPLE_TP_SSL, CSSM_KEYUSE_SIGN, TRUE, validOnly); if (policyFlags & (1 << 1)) do_identity_search_with_policy(keychainOrArray, name, &CSSMOID_APPLE_TP_SSL, CSSM_KEYUSE_SIGN, FALSE, validOnly); if (policyFlags & (1 << 2)) do_identity_search_with_policy(keychainOrArray, name, &CSSMOID_APPLE_TP_SMIME, CSSM_KEYUSE_SIGN, TRUE, validOnly); if (policyFlags & (1 << 3)) do_identity_search_with_policy(keychainOrArray, name, &CSSMOID_APPLE_TP_EAP, CSSM_KEYUSE_SIGN, TRUE, validOnly); if (policyFlags & (1 << 4)) do_identity_search_with_policy(keychainOrArray, name, &CSSMOID_APPLE_TP_IP_SEC, CSSM_KEYUSE_SIGN, TRUE, validOnly); if (policyFlags & (1 << 5)) do_identity_search_with_policy(keychainOrArray, name, &CSSMOID_APPLE_TP_ICHAT, CSSM_KEYUSE_SIGN, TRUE, validOnly); if (policyFlags & (1 << 6)) do_identity_search_with_policy(keychainOrArray, name, &CSSMOID_APPLE_TP_CODE_SIGNING, CSSM_KEYUSE_SIGN, TRUE, validOnly); if (policyFlags & (1 << 7)) do_identity_search_with_policy(keychainOrArray, name, &CSSMOID_APPLE_X509_BASIC, CSSM_KEYUSE_SIGN, TRUE, validOnly); if (policyFlags & (1 << 8)) do_system_identity_search(kSecIdentityDomainDefault); if (policyFlags & (1 << 9)) do_system_identity_search(kSecIdentityDomainKerberosKDC); if (policyFlags & (1 << 11)) // 10 RFE do_identity_search_with_policy(keychainOrArray, name, &CSSMOID_APPLE_TP_MACAPPSTORE_RECEIPT, CSSM_KEYUSE_SIGN, TRUE, validOnly); return result; } int keychain_find_identity(int argc, char * const *argv) { int ch, result = 0; unsigned int policyFlags = 0; const char *name = NULL; Boolean validOnly = FALSE; CFTypeRef keychainOrArray = NULL; /* * " -p Specify policy to evaluate (multiple -p options are allowed)\n" * " -s Specify optional policy-specific string (e.g. a DNS hostname for SSL,\n" * " or RFC822 email address for S/MIME)\n" * " -v Show valid identities only (default is to show all identities)\n" */ while ((ch = getopt(argc, argv, "hp:s:v")) != -1) { switch (ch) { case 'p': if (optarg != NULL) { if (!strcmp(optarg, "ssl-client")) policyFlags |= 1 << 0; else if (!strcmp(optarg, "ssl-server")) policyFlags |= 1 << 1; else if (!strcmp(optarg, "smime")) policyFlags |= 1 << 2; else if (!strcmp(optarg, "eap")) policyFlags |= 1 << 3; else if (!strcmp(optarg, "ipsec")) policyFlags |= 1 << 4; else if (!strcmp(optarg, "ichat")) policyFlags |= 1 << 5; else if (!strcmp(optarg, "codesigning")) policyFlags |= 1 << 6; else if (!strcmp(optarg, "basic")) policyFlags |= 1 << 7; else if (!strcmp(optarg, "sys-default")) policyFlags |= 1 << 8; else if (!strcmp(optarg, "sys-kerberos-kdc")) policyFlags |= 1 << 9; else if (!strcmp(optarg, "macappstore")) policyFlags |= 1 << 11; else { result = 2; /* @@@ Return 2 triggers usage message. */ goto cleanup; } } break; case 's': name = optarg; break; case 'v': validOnly = TRUE; break; case '?': default: result = 2; /* @@@ Return 2 triggers usage message. */ goto cleanup; } } if (!policyFlags) policyFlags |= 1 << 7; /* default to basic policy if none specified */ argc -= optind; argv += optind; keychainOrArray = keychain_create_array(argc, argv); result = do_find_identities(keychainOrArray, name, policyFlags, validOnly); cleanup: safe_CFRelease(&keychainOrArray); return result; }