keychain_list.c   [plain text]


/*
 * Copyright (c) 2003-2010,2012,2014 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@
 *
 * keychain_list.c
 */

#include "keychain_list.h"

#include "keychain_utilities.h"
#include "security_tool.h"

#include <stdio.h>
#include <string.h>
#include <sys/param.h>
#include <unistd.h>
#include <CoreFoundation/CFArray.h>
#include <Security/SecKeychain.h>

// SecKeychainCopyLogin
#include <Security/SecKeychainPriv.h>
#include <Security/SecItem.h>

typedef enum
{
	kList,
	kAdd,
	kRemove,
	kSet,
} list_operation;

static void
display_name(const void *value, void *context)
{
	SecKeychainRef keychain = (SecKeychainRef)value;
	UInt32 pathLength = MAXPATHLEN;
	char pathName[MAXPATHLEN + 1];
	OSStatus result = SecKeychainGetPath(keychain, &pathLength, pathName);
	if (result)
		sec_error("SecKeychainGetPath %p: %s", keychain, sec_errstr(result));
	else
		fprintf(stdout, "    \"%*s\"\n", (int)pathLength, pathName);
}


static void
display_list(const char *desc, CFTypeRef keychainOrArray)
{
	if (desc && strlen(desc))
		fprintf(stdout, "%s\n", desc);

	if (!keychainOrArray)
	{
		fprintf(stdout, " <NULL>\n");
	}
	else if (CFGetTypeID(keychainOrArray) == SecKeychainGetTypeID())
	{
		display_name(keychainOrArray, NULL);
	}
	else
	{
		CFArrayRef array = (CFArrayRef)keychainOrArray;
		CFRange range = { 0, CFArrayGetCount(array) };
		CFArrayApplyFunction(array, range, display_name, NULL);
	}
}

static int
parse_domain(const char *name, SecPreferencesDomain *domain)
{
	size_t len = strlen(name);

	if (!strncmp("user", name, len))
		*domain = kSecPreferencesDomainUser;
	else if (!strncmp("system", name, len))
		*domain = kSecPreferencesDomainSystem;
	else if (!strncmp("common", name, len))
		*domain = kSecPreferencesDomainCommon;
	else if (!strncmp("dynamic", name, len))
		*domain = kSecPreferencesDomainDynamic;
	else
	{
		sec_error("Invalid domain: %s", name);
		return SHOW_USAGE_MESSAGE;
	}

	return 0;
}

static const char *
domain2str(SecPreferencesDomain domain)
{
	switch (domain)
	{
	case kSecPreferencesDomainUser:
		return "user";
	case kSecPreferencesDomainSystem:
		return "system";
	case kSecPreferencesDomainCommon:
		return "common";
	case kSecPreferencesDomainDynamic:
		return "dynamic";
	default:
		return "unknown";
	}
}

static void
keychain_ctk_list_item(CFTypeRef item)
{
    char buf[128] = { 0 };

    CFTypeID tid = CFGetTypeID(item);
    if (tid == CFDictionaryGetTypeID()) {
        CFStringRef tkid = CFDictionaryGetValue((CFDictionaryRef)item, CFSTR("tkid"));
        if (CFStringGetCString(tkid, buf, sizeof(buf), kCFStringEncodingUTF8)) {
            fprintf(stdout, "%s\n", buf);
        }
    } else {
        fprintf(stderr, "Unexpected item.");
    }
}

static void
keychain_ctk_list_items(CFArrayRef items)
{
    CFMutableSetRef displayedItemsRef = CFSetCreateMutable(kCFAllocatorDefault, CFArrayGetCount(items), &kCFTypeSetCallBacks);

    for (CFIndex i = 0; i < CFArrayGetCount(items); i++) {
        CFDictionaryRef item = CFArrayGetValueAtIndex(items, i);

        if (CFGetTypeID(item) == CFDictionaryGetTypeID()) {
            CFStringRef tkid = CFDictionaryGetValue((CFDictionaryRef)item, CFSTR("tkid"));

            if (tkid && !CFSetContainsValue(displayedItemsRef, tkid)) {
                keychain_ctk_list_item(CFArrayGetValueAtIndex(items, i));
                CFSetAddValue(displayedItemsRef, tkid);
            }
        } else {
            keychain_ctk_list_item(item);
        }
    }

    CFRelease(displayedItemsRef);
}

int
keychain_list(int argc, char * const *argv)
{
	CFTypeRef keychainOrArray = NULL;
	CFArrayRef searchList = NULL;
	list_operation operation = kList;
	SecPreferencesDomain domain = kSecPreferencesDomainUser;
	Boolean use_domain = false;
	int ch, result = 0;
	OSStatus status;

	while ((ch = getopt(argc, argv, "d:hs")) != -1)
	{
		switch  (ch)
		{
		case 'd':
			result = parse_domain(optarg, &domain);
			if (result)
				return result;
			use_domain = true;
			break;
		case 's':
			operation = kSet;
			break;
		case '?':
		default:
			return SHOW_USAGE_MESSAGE;
		}
	}

	argc -= optind;
	argv += optind;

	switch (operation)
	{
	case kAdd:
		result = 1;
		break;
	case kRemove:
		result = 1;
		break;
	case kList:
		if (argc > 0)
			result = 2; // Show usage
		else
		{
			if (use_domain)
			{
				status = SecKeychainCopyDomainSearchList(domain, &searchList);
				if (status)
				{
					sec_error("SecKeychainCopyDomainSearchList %s: %s", domain2str(domain), sec_errstr(status));
					result = 1;
				}
				else
				{
#if 0
					fprintf(stdout, "%s search list: ", domain2str(domain));
#endif
					display_list("", searchList);
				}
			}
			else
			{
				status = SecKeychainCopySearchList(&searchList);
				if (status)
				{
					sec_perror("SecKeychainCopySearchList", status);
					result = 1;
				}
				else
				{
#if 0
					display_list("search list:", searchList);
#else
					display_list("", searchList);
#endif
				}
			}
		}
		break;
	case kSet:
		keychainOrArray = keychain_create_array(argc, argv);
		if (argc == 0)
			searchList = CFArrayCreate(NULL, NULL, 0, &kCFTypeArrayCallBacks);
		else if (argc == 1)
			searchList = CFArrayCreate(NULL, &keychainOrArray, 1, &kCFTypeArrayCallBacks);
		else
			searchList = (CFArrayRef)CFRetain(keychainOrArray);

        if (use_domain)
        {
            status = SecKeychainSetDomainSearchList(domain, searchList);
            if (status)
            {
                sec_error("SecKeychainSetDomainSearchList %s: %s", domain2str(domain), sec_errstr(status));
                result = 1;
            }
        }
        else
        {
            status = SecKeychainSetSearchList(searchList);
            if (status)
            {
                sec_perror("SecKeychainSetSearchList", status);
                result = 1;
            }
        }
		break;
	}

	if (keychainOrArray)
		CFRelease(keychainOrArray);
	if (searchList)
		CFRelease(searchList);

	return result;
}

int
ctk_list(int argc, char * const *argv)
{
    OSStatus stat = errSecSuccess;
    CFDictionaryRef query = NULL;
    CFTypeRef result = NULL;

    const void *keys[] = {
        kSecClass,
        kSecMatchLimit,
        kSecAttrAccessGroup,
        kSecReturnAttributes
    };

    const void *values[] = {
        kSecClassIdentity,
        kSecMatchLimitAll,
        kSecAttrAccessGroupToken,
        kCFBooleanTrue
    };

    query = CFDictionaryCreate(kCFAllocatorDefault,
                               keys,
                               values,
                               sizeof(values) / sizeof(values[0]),
                               &kCFTypeDictionaryKeyCallBacks,
                               &kCFTypeDictionaryValueCallBacks);

    stat = SecItemCopyMatching(query, (CFTypeRef *)&result);
    if(stat) {
        if (stat == errSecItemNotFound) {
            fprintf(stderr, "No smartcards found.\n");
        } else {
            sec_error("SecItemCopyMatching: %x (%d) - %s", stat, stat, sec_errstr(stat));
        }
        goto cleanup;
    }


    if (CFGetTypeID(result) == CFArrayGetTypeID()) {
        keychain_ctk_list_items((CFArrayRef)result);
    } else {
        keychain_ctk_list_item(result);
    }

cleanup:
    if(query) {
        CFRelease(query);
    }

    if(result) {
        CFRelease(result);
    }
    
    return stat;
}

int
keychain_default(int argc, char * const *argv)
{
	SecPreferencesDomain domain = kSecPreferencesDomainUser;
	SecKeychainRef keychain = NULL;
	Boolean use_domain = false;
	Boolean do_set = false;
	int ch, result = 0;
	OSStatus status;

	while ((ch = getopt(argc, argv, "d:hs")) != -1)
	{
		switch  (ch)
		{
		case 'd':
			result = parse_domain(optarg, &domain);
			if (result)
				return result;
			use_domain = true;
			break;
		case 's':
			do_set = true;
			break;
		case '?':
		default:
			return SHOW_USAGE_MESSAGE;
		}
	}

	argc -= optind;
	argv += optind;

	if (do_set)
	{
		if (argc == 1)
			keychain = (SecKeychainRef)keychain_create_array(argc, argv);
		else if (argc > 0)
			return SHOW_USAGE_MESSAGE;

		if (use_domain)
		{
			status = SecKeychainSetDomainDefault(domain, keychain);
			if (status)
			{
				sec_error("SecKeychainSetDomainDefault %s: %s", domain2str(domain), sec_errstr(status));
				result = 1;
			}
		}
		else
		{
			status = SecKeychainSetDefault(keychain);
			if (status)
			{
				sec_perror("SecKeychainSetDefault", status);
				result = 1;
			}
		}
	}
	else
	{
		if (argc > 0)
			return SHOW_USAGE_MESSAGE;

		if (use_domain)
		{
			status = SecKeychainCopyDomainDefault(domain, &keychain);
			if (status)
			{
				sec_error("SecKeychainCopyDomainDefault %s: %s", domain2str(domain), sec_errstr(status));
				result = 1;
			}
			else
			{
#if 0
				fprintf(stdout, "default %s keychain: ", domain2str(domain));
#endif
				display_list("", keychain);
			}
		}
		else
		{
			status = SecKeychainCopyDefault(&keychain);
			if (status)
			{
				sec_perror("SecKeychainCopyDefault", status);
				result = 1;
			}
			else
			{
#if 0
				display_list("default keychain: ", keychain);
#else
				display_list("", keychain);
#endif
			}
		}
	}

	if (keychain)
		CFRelease(keychain);

	return result;
}

int
keychain_login(int argc, char * const *argv)
{
	SecPreferencesDomain domain = kSecPreferencesDomainUser;
	SecKeychainRef keychain = NULL;
	Boolean use_domain = false;
	Boolean do_set = false;
	int ch, result = 0;
	OSStatus status;

	while ((ch = getopt(argc, argv, "d:hs")) != -1)
	{
		switch  (ch)
		{
		case 'd':
			result = parse_domain(optarg, &domain);
			if (result)
				return result;
			use_domain = true;
			break;
		case 's':
			do_set = true;
			break;
		case '?':
		default:
			return SHOW_USAGE_MESSAGE;
		}
	}

	argc -= optind;
	argv += optind;

	if (do_set)
	{
		if (argc == 1)
			keychain = (SecKeychainRef)keychain_create_array(argc, argv);
		else if (argc > 0)
			return SHOW_USAGE_MESSAGE;

#if 0
		if (use_domain)
		{
			status = SecKeychainSetDomainLogin(domain, keychain);
			if (status)
			{
				sec_error("SecKeychainSetDomainLogin %s: %s", domain2str(domain), sec_errstr(status));
				result = 1;
			}
		}
		else
		{
			status = SecKeychainSetLogin(keychain);
			if (status)
			{
				sec_perror("SecKeychainSetLogin", status);
				result = 1;
			}
		}
#else
		result = 1;
#endif
	}
	else
	{
		if (argc > 0)
			return SHOW_USAGE_MESSAGE;

		if (use_domain)
		{
#if 0
			status = SecKeychainCopyDomainLogin(domain, &keychain);
			if (status)
			{
				sec_error("SecKeychainCopyDomainLogin %s: %s", domain2str(domain), sec_errstr(status));
				result = 1;
			}
			else
			{
#if 0
				fprintf(stdout, "login %s keychain: ", domain2str(domain));
#endif
				display_list("", keychain);
			}
#else
			result = 1;
#endif
		}
		else
		{
			status = SecKeychainCopyLogin(&keychain);
			if (status)
			{
				sec_perror("SecKeychainCopyLogin", status);
				result = 1;
			}
			else
			{
#if 0
				display_list("login keychain: ", keychain);
#else
				display_list("", keychain);
#endif
			}
		}
	}

	if (keychain)
		CFRelease(keychain);

	return result;
}