+/*
+ * Copyright (c) 2003-2009,2012,2014-2019 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_utilities.c
+ */
+
+#include "keychain_utilities.h"
+#include "security_tool.h"
+
+#include <Security/cssmapi.h>
+#include <Security/SecAccess.h>
+#include <Security/SecACL.h>
+#include <Security/SecTrustedApplication.h>
+#include <Security/SecKeychainItem.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/param.h>
+#include <libkern/OSByteOrder.h>
+#include <utilities/SecCFRelease.h>
+
+#include "readline_cssm.h"
+
+// SecTrustedApplicationValidateWithPath
+#include <Security/SecTrustedApplicationPriv.h>
+#include <Security/SecKeychainPriv.h>
+
+
+void check_obsolete_keychain(const char *kcName)
+{
+ if(kcName == NULL) {
+ return;
+ }
+ if(!strcmp(kcName, "/System/Library/Keychains/X509Anchors")) {
+ fprintf(stderr, "***************************************************************\n");
+ fprintf(stderr, " WARNING\n");
+ fprintf(stderr, "\n");
+ fprintf(stderr, "The keychain you are accessing, X509Anchors, is no longer\n");
+ fprintf(stderr, "used by Mac OS X as the system root certificate store.\n");
+ fprintf(stderr, "Please read the security man page for information on the \n");
+ fprintf(stderr, "add-trusted-cert command. New system root certificates should\n");
+ fprintf(stderr, "be added to the Admin Trust Settings domain and to the \n");
+ fprintf(stderr, "System keychain in /Library/Keychains.\n");
+ fprintf(stderr, "***************************************************************\n");
+ }
+ else if(!strcmp(kcName, "/System/Library/Keychains/X509Certificates")) {
+ fprintf(stderr, "***************************************************************\n");
+ fprintf(stderr, " WARNING\n");
+ fprintf(stderr, "\n");
+ fprintf(stderr, "The keychain you are accessing, X509Certificates, is no longer\n");
+ fprintf(stderr, "used by Mac OS X as the system intermediate certificate\n");
+ fprintf(stderr, "store. New system intermediate certificates should be added\n");
+ fprintf(stderr, "to the System keychain in /Library/Keychains.\n");
+ fprintf(stderr, "***************************************************************\n");
+ }
+}
+
+SecKeychainRef CF_RETURNS_RETAINED
+keychain_open(const char *name)
+{
+ SecKeychainRef keychain = NULL;
+ OSStatus result;
+
+ check_obsolete_keychain(name);
+ if (name && name[0] != '/')
+ {
+ CFArrayRef dynamic = NULL;
+ result = SecKeychainCopyDomainSearchList(
+ kSecPreferencesDomainDynamic, &dynamic);
+ if (result)
+ {
+ sec_error("SecKeychainCopyDomainSearchList %s: %s",
+ name, sec_errstr(result));
+ return NULL;
+ }
+ else
+ {
+ uint32_t i;
+ CFIndex count = dynamic ? CFArrayGetCount(dynamic) : 0;
+
+ for (i = 0; i < count; ++i)
+ {
+ char pathName[MAXPATHLEN];
+ UInt32 ioPathLength = sizeof(pathName);
+ bzero(pathName, ioPathLength);
+ keychain = (SecKeychainRef)CFArrayGetValueAtIndex(dynamic, i);
+ result = SecKeychainGetPath(keychain, &ioPathLength, pathName);
+ if (result)
+ {
+ sec_error("SecKeychainGetPath %s: %s",
+ name, sec_errstr(result));
+ return NULL;
+ }
+ if (!strncmp(pathName, name, ioPathLength))
+ {
+ CFRetain(keychain);
+ CFRelease(dynamic);
+ return keychain;
+ }
+ }
+ CFReleaseNull(dynamic);
+ }
+ }
+
+ if(name) {
+ result = SecKeychainOpen(name, &keychain);
+ } else {
+ result = errSecParam;
+ }
+ if (result)
+ {
+ sec_error("SecKeychainOpen %s: %s", name, sec_errstr(result));
+ }
+
+ return keychain;
+}
+
+CFTypeRef
+keychain_create_array(int argc, char * const *argv)
+{
+ if (argc == 0)
+ return NULL;
+ else if (argc == 1)
+ return keychain_open(argv[0]);
+ else
+ {
+ CFMutableArrayRef keychains = CFArrayCreateMutable(NULL, argc, &kCFTypeArrayCallBacks);
+ int ix;
+ for (ix = 0; ix < argc; ++ix)
+ {
+ SecKeychainRef keychain = keychain_open(argv[ix]);
+ if (keychain)
+ {
+ CFArrayAppendValue(keychains, keychain);
+ CFRelease(keychain);
+ }
+ }
+
+ return keychains;
+ }
+}
+
+int
+parse_fourcharcode(const char *name, UInt32 *code)
+{
+ UInt32 cc = 0;
+ size_t len = (name) ? strlen(name) : 0;
+
+ // error check the name
+ if (len != 4)
+ {
+ fprintf(stderr, "Error: four-character types must be exactly 4 characters long.\n");
+ if (len == 3) {
+ fprintf(stderr, "(Try \"%s \" instead of \"%s\")\n", name, name);
+ }
+ return 1;
+ }
+
+ int i;
+ for (i = 0; i < 4; ++i)
+ {
+ cc = (cc << 8) | name[i];
+ }
+
+ *code = cc; // note: this is in host byte order, suitable for passing to APIs
+
+ return 0;
+}
+
+int
+print_keychain_name(FILE *stream, SecKeychainRef keychain)
+{
+ int result = 0;
+ char pathName[MAXPATHLEN];
+ UInt32 ioPathLength = sizeof(pathName);
+ OSStatus status = SecKeychainGetPath(keychain, &ioPathLength, pathName);
+ if (status)
+ {
+ sec_perror("SecKeychainGetPath", status);
+ result = 1;
+ goto loser;
+ }
+
+ print_buffer(stream, ioPathLength, pathName);
+
+loser:
+ return result;
+}
+
+static int
+print_keychain_version(FILE* stream, SecKeychainRef keychain)
+{
+ int result = 0;
+ UInt32 version;
+ OSStatus status = SecKeychainGetKeychainVersion(keychain, &version);
+ if(status) {
+ sec_perror("SecKeychainGetKeychainVersion", status);
+ result = 1;
+ goto loser;
+ }
+
+ fprintf(stream, "%d", (uint32_t) version);
+
+loser:
+ return result;
+}
+
+static void
+print_cfdata(FILE *stream, CFDataRef data)
+{
+ if (data)
+ return print_buffer(stream, CFDataGetLength(data), CFDataGetBytePtr(data));
+ else
+ fprintf(stream, "<NULL>");
+}
+
+void
+print_cfstring(FILE *stream, CFStringRef string)
+{
+ if (!string)
+ fprintf(stream, "<NULL>");
+ else
+ {
+ const char *utf8 = CFStringGetCStringPtr(string, kCFStringEncodingUTF8);
+ if (utf8)
+ fprintf(stream, "%s", utf8);
+ else
+ {
+ CFRange rangeToProcess = CFRangeMake(0, CFStringGetLength(string));
+ while (rangeToProcess.length > 0)
+ {
+ UInt8 localBuffer[256];
+ CFIndex usedBufferLength;
+ CFIndex numChars = CFStringGetBytes(string, rangeToProcess,
+ kCFStringEncodingUTF8, '?', FALSE, localBuffer,
+ sizeof(localBuffer), &usedBufferLength);
+ if (numChars == 0)
+ break; // Failed to convert anything...
+
+ fprintf(stream, "%.*s", (int)usedBufferLength, localBuffer);
+ rangeToProcess.location += numChars;
+ rangeToProcess.length -= numChars;
+ }
+ }
+ }
+}
+
+static int
+print_access(FILE *stream, SecAccessRef access, Boolean interactive)
+{
+ CFArrayRef aclList = NULL;
+ CFIndex aclix, aclCount;
+ int result = 0;
+ OSStatus status;
+
+ status = SecAccessCopyACLList(access, &aclList);
+ if (status)
+ {
+ sec_perror("SecAccessCopyACLList", status);
+ result = 1;
+ goto loser;
+ }
+
+ aclCount = CFArrayGetCount(aclList);
+ fprintf(stream, "access: %lu entries\n", aclCount);
+ for (aclix = 0; aclix < aclCount; ++aclix)
+ {
+ CFArrayRef applicationList = NULL;
+ CFStringRef description = NULL;
+ CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR promptSelector = {};
+ CFIndex appix, appCount;
+
+ SecACLRef acl = (SecACLRef)CFArrayGetValueAtIndex(aclList, aclix);
+ CSSM_ACL_AUTHORIZATION_TAG tags[64]; // Pick some upper limit
+ uint32 tagix, tagCount = sizeof(tags) / sizeof(*tags);
+ status = SecACLGetAuthorizations(acl, tags, &tagCount);
+ if (status)
+ {
+ sec_perror("SecACLGetAuthorizations", status);
+ result = 1;
+ goto loser;
+ }
+
+ fprintf(stream, " entry %lu:\n authorizations (%lu):", aclix,
+ (unsigned long)tagCount);
+ bool printPartitionIDList = false;
+ for (tagix = 0; tagix < tagCount; ++tagix)
+ {
+ CSSM_ACL_AUTHORIZATION_TAG tag = tags[tagix];
+ switch (tag)
+ {
+ case CSSM_ACL_AUTHORIZATION_ANY:
+ fputs(" any", stream);
+ break;
+ case CSSM_ACL_AUTHORIZATION_LOGIN:
+ fputs(" login", stream);
+ break;
+ case CSSM_ACL_AUTHORIZATION_GENKEY:
+ fputs(" genkey", stream);
+ break;
+ case CSSM_ACL_AUTHORIZATION_DELETE:
+ fputs(" delete", stream);
+ break;
+ case CSSM_ACL_AUTHORIZATION_EXPORT_WRAPPED:
+ fputs(" export_wrapped", stream);
+ break;
+ case CSSM_ACL_AUTHORIZATION_EXPORT_CLEAR:
+ fputs(" export_clear", stream);
+ break;
+ case CSSM_ACL_AUTHORIZATION_IMPORT_WRAPPED:
+ fputs(" import_wrapped", stream);
+ break;
+ case CSSM_ACL_AUTHORIZATION_IMPORT_CLEAR:
+ fputs(" import_clear", stream);
+ break;
+ case CSSM_ACL_AUTHORIZATION_SIGN:
+ fputs(" sign", stream);
+ break;
+ case CSSM_ACL_AUTHORIZATION_ENCRYPT:
+ fputs(" encrypt", stream);
+ break;
+ case CSSM_ACL_AUTHORIZATION_DECRYPT:
+ fputs(" decrypt", stream);
+ break;
+ case CSSM_ACL_AUTHORIZATION_MAC:
+ fputs(" mac", stream);
+ break;
+ case CSSM_ACL_AUTHORIZATION_DERIVE:
+ fputs(" derive", stream);
+ break;
+ case CSSM_ACL_AUTHORIZATION_DBS_CREATE:
+ fputs(" dbs_create", stream);
+ break;
+ case CSSM_ACL_AUTHORIZATION_DBS_DELETE:
+ fputs(" dbs_delete", stream);
+ break;
+ case CSSM_ACL_AUTHORIZATION_DB_READ:
+ fputs(" db_read", stream);
+ break;
+ case CSSM_ACL_AUTHORIZATION_DB_INSERT:
+ fputs(" db_insert", stream);
+ break;
+ case CSSM_ACL_AUTHORIZATION_DB_MODIFY:
+ fputs(" db_modify", stream);
+ break;
+ case CSSM_ACL_AUTHORIZATION_DB_DELETE:
+ fputs(" db_delete", stream);
+ break;
+ case CSSM_ACL_AUTHORIZATION_CHANGE_ACL:
+ fputs(" change_acl", stream);
+ break;
+ case CSSM_ACL_AUTHORIZATION_CHANGE_OWNER:
+ fputs(" change_owner", stream);
+ break;
+ case CSSM_ACL_AUTHORIZATION_INTEGRITY:
+ fputs(" integrity", stream);
+ break;
+ case CSSM_ACL_AUTHORIZATION_PARTITION_ID:
+ fputs(" partition_id", stream);
+ printPartitionIDList = true;
+ break;
+ default:
+ fprintf(stream, " tag=%lu", (unsigned long)tag);
+ break;
+ }
+ }
+ fputc('\n', stream);
+
+ status = SecACLCopySimpleContents(acl, &applicationList, &description, &promptSelector);
+ if (status)
+ {
+ sec_perror("SecACLCopySimpleContents", status);
+ continue;
+ }
+
+ if (promptSelector.flags & CSSM_ACL_KEYCHAIN_PROMPT_REQUIRE_PASSPHRASE)
+ fputs(" require-password\n", stream);
+ else
+ fputs(" don't-require-password\n", stream);
+
+ fputs(" description: ", stream);
+ // special case for Partition IDs
+ if(printPartitionIDList) {
+ print_partition_id_list(stream, description);
+ } else {
+ print_cfstring(stream, description);
+ }
+ fputc('\n', stream);
+
+ if (applicationList)
+ {
+ appCount = CFArrayGetCount(applicationList);
+ fprintf(stream, " applications (%lu):\n", appCount);
+ }
+ else
+ {
+ appCount = 0;
+ fprintf(stream, " applications: <null>\n");
+ }
+
+ for (appix = 0; appix < appCount; ++appix)
+ {
+ const UInt8* bytes;
+ SecTrustedApplicationRef app = (SecTrustedApplicationRef)CFArrayGetValueAtIndex(applicationList, appix);
+ CFDataRef data = NULL;
+ fprintf(stream, " %lu: ", appix);
+ status = SecTrustedApplicationCopyData(app, &data);
+ if (status)
+ {
+ sec_perror("SecTrustedApplicationCopyData", status);
+ continue;
+ }
+
+ bytes = CFDataGetBytePtr(data);
+ if (bytes && bytes[0] == 0x2f) {
+ fprintf(stream, "%s", (const char *)bytes);
+ if ((status = SecTrustedApplicationValidateWithPath(app, (const char *)bytes)) == noErr) {
+ fprintf(stream, " (OK)");
+ } else {
+ fprintf(stream, " (status %d)", (int)status);
+ }
+ fprintf(stream, "\n");
+ } else {
+ print_cfdata(stream, data);
+ fputc('\n', stream);
+ }
+ if (data)
+ CFRelease(data);
+ }
+
+ if (applicationList)
+ CFRelease(applicationList);
+
+ if (description)
+ CFRelease(description);
+
+ if (interactive)
+ {
+ char buffer[10] = {};
+ fprintf(stderr, "Remove this acl? ");
+ if (readline(buffer, sizeof(buffer)) && buffer[0] == 'y')
+ {
+ fprintf(stderr, "removing acl\n");
+ status = SecACLRemove(acl);
+ if (status)
+ {
+ sec_perror("SecACLRemove", status);
+ continue;
+ }
+ }
+ }
+ }
+
+loser:
+ if (aclList)
+ CFRelease(aclList);
+
+ return result;
+}
+
+int
+print_keychain_item_attributes(FILE *stream, SecKeychainItemRef item, Boolean show_data, Boolean show_raw_data, Boolean show_acl, Boolean interactive)
+{
+ int result = 0;
+ unsigned int ix;
+ OSStatus status;
+ SecKeychainRef keychain = NULL;
+ SecAccessRef access = NULL;
+ SecItemClass itemClass = 0;
+ UInt32 itemID;
+ SecKeychainAttributeList *attrList = NULL;
+ SecKeychainAttributeInfo *info = NULL;
+ UInt32 length = 0;
+ void *data = NULL;
+
+ status = SecKeychainItemCopyKeychain(item, &keychain);
+ if (status)
+ {
+ sec_perror("SecKeychainItemCopyKeychain", status);
+ result = 1;
+ goto loser;
+ }
+
+ fputs("keychain: ", stream);
+ result = print_keychain_name(stream, keychain);
+ fputc('\n', stream);
+ if (result)
+ goto loser;
+ fputs("version: ", stream);
+ result = print_keychain_version(stream, keychain);
+ fputc('\n', stream);
+ if (result)
+ goto loser;
+
+ /* First find out the item class. */
+ status = SecKeychainItemCopyAttributesAndData(item, NULL, &itemClass, NULL, NULL, NULL);
+ if (status)
+ {
+ sec_perror("SecKeychainItemCopyAttributesAndData", status);
+ result = 1;
+ goto loser;
+ }
+
+ fputs("class: ", stream);
+ char buffer[4];
+ buffer[3] = itemClass & 0xFF;
+ buffer[2] = (itemClass >> 8) & 0xFF;
+ buffer[1] = (itemClass >> 16) & 0xFF;
+ buffer[0] = (itemClass >> 24) & 0xFF;
+
+ print_buffer(stream, 4, buffer);
+ fputs("\nattributes:\n", stream);
+
+ switch (itemClass)
+ {
+ case kSecInternetPasswordItemClass:
+ itemID = CSSM_DL_DB_RECORD_INTERNET_PASSWORD;
+ break;
+ case kSecGenericPasswordItemClass:
+ itemID = CSSM_DL_DB_RECORD_GENERIC_PASSWORD;
+ break;
+ case 'ashp': /* kSecAppleSharePasswordItemClass */
+ itemID = CSSM_DL_DB_RECORD_APPLESHARE_PASSWORD;
+ break;
+ default:
+ itemID = itemClass;
+ break;
+ }
+
+ /* Now get the AttributeInfo for it. */
+ status = SecKeychainAttributeInfoForItemID(keychain, itemID, &info);
+ if (status)
+ {
+ sec_perror("SecKeychainAttributeInfoForItemID", status);
+ result = 1;
+ goto loser;
+ }
+
+ status = SecKeychainItemCopyAttributesAndData(item, info, &itemClass, &attrList,
+ show_data ? &length : NULL,
+ show_data ? &data : NULL);
+ if (status)
+ {
+ sec_perror("SecKeychainItemCopyAttributesAndData", status);
+ result = 1;
+ goto loser;
+ }
+
+ if (info->count != attrList->count)
+ {
+ sec_error("info count: %ld != attribute count: %ld", info->count, attrList->count);
+ result = 1;
+ goto loser;
+ }
+
+ for (ix = 0; ix < info->count; ++ix)
+ {
+ UInt32 tag = info->tag[ix];
+ UInt32 format = info->format[ix];
+ SecKeychainAttribute *attribute = &attrList->attr[ix];
+ if (tag != attribute->tag)
+ {
+ sec_error("attribute %d of %ld info tag: %ld != attribute tag: %ld", ix, info->count, tag, attribute->tag);
+ result = 1;
+ goto loser;
+ }
+
+ fputs(" ", stream);
+ print_uint32(stream, tag);
+ switch (format)
+ {
+ case CSSM_DB_ATTRIBUTE_FORMAT_STRING:
+ fputs("<string>", stream);
+ break;
+ case CSSM_DB_ATTRIBUTE_FORMAT_SINT32:
+ fputs("<sint32>", stream);
+ break;
+ case CSSM_DB_ATTRIBUTE_FORMAT_UINT32:
+ fputs("<uint32>", stream);
+ break;
+ case CSSM_DB_ATTRIBUTE_FORMAT_BIG_NUM:
+ fputs("<bignum>", stream);
+ break;
+ case CSSM_DB_ATTRIBUTE_FORMAT_REAL:
+ fputs("<real>", stream);
+ break;
+ case CSSM_DB_ATTRIBUTE_FORMAT_TIME_DATE:
+ fputs("<timedate>", stream);
+ break;
+ case CSSM_DB_ATTRIBUTE_FORMAT_BLOB:
+ fputs("<blob>", stream);
+ break;
+ case CSSM_DB_ATTRIBUTE_FORMAT_MULTI_UINT32:
+ fputs("<uint32>", stream);
+ break;
+ case CSSM_DB_ATTRIBUTE_FORMAT_COMPLEX:
+ fputs("<complex>", stream);
+ break;
+ default:
+ fprintf(stream, "<format: %d>", (int)format);
+ break;
+ }
+ fputs("=", stream);
+ if (!attribute->length && !attribute->data)
+ fputs("<NULL>", stream);
+ else
+ { switch (format)
+ {
+ case CSSM_DB_ATTRIBUTE_FORMAT_SINT32:
+ case CSSM_DB_ATTRIBUTE_FORMAT_UINT32:
+ {
+ print_uint32(stream, *(UInt32*) attribute->data);
+ break;
+ }
+
+ case CSSM_DB_ATTRIBUTE_FORMAT_MULTI_UINT32:
+ {
+ int n = attribute->length / sizeof(UInt32);
+ UInt32* ptr = (UInt32*) attribute->data;
+
+ while (n--)
+ {
+ print_uint32(stream, *ptr++);
+ }
+ }
+ break;
+
+ default:
+ {
+ print_buffer(stream, attribute->length, attribute->data);
+ }
+ break;
+ }
+ }
+ fputc('\n', stream);
+ }
+
+ if (show_data)
+ {
+ fputs("data:\n", stream);
+ print_buffer(stream, length, data);
+ fputc('\n', stream);
+ }
+
+ if (show_raw_data)
+ {
+ CSSM_DL_DB_HANDLE dldbHandle = {};
+ const CSSM_DB_UNIQUE_RECORD *uniqueRecordID = NULL;
+ CSSM_DATA data = {};
+ status = SecKeychainItemGetDLDBHandle(item, &dldbHandle);
+ if (status)
+ {
+ sec_perror("SecKeychainItemGetDLDBHandle", status);
+ result = 1;
+ goto loser;
+ }
+
+ status = SecKeychainItemGetUniqueRecordID(item, &uniqueRecordID);
+ if (status)
+ {
+ sec_perror("SecKeychainItemGetUniqueRecordID", status);
+ result = 1;
+ goto loser;
+ }
+
+ status = CSSM_DL_DataGetFromUniqueRecordId(dldbHandle, uniqueRecordID, NULL, &data);
+ if (status)
+ {
+ sec_perror("CSSM_DL_DataGetFromUniqueRecordId", status);
+ result = 1;
+ goto loser;
+ }
+
+ fputs("raw data:\n", stream);
+ print_buffer(stream, data.Length, data.Data);
+ fputc('\n', stream);
+
+ /* @@@ Hmm which allocators should we use here? */
+ free(data.Data);
+ }
+
+ if (show_acl)
+ {
+ status = SecKeychainItemCopyAccess(item, &access);
+ if (status == errSecNoAccessForItem)
+ fprintf(stream, "no access control for this item\n");
+ else
+ {
+ if (status)
+ {
+ sec_perror("SecKeychainItemCopyAccess", status);
+ result = 1;
+ goto loser;
+ }
+
+ result = print_access(stream, access, interactive);
+ if (result)
+ goto loser;
+
+ if (interactive)
+ {
+ char buffer[10] = {};
+ fprintf(stderr, "Update access? ");
+ if (readline(buffer, sizeof(buffer)) && buffer[0] == 'y')
+ {
+ fprintf(stderr, "Updating access\n");
+ status = SecKeychainItemSetAccess(item, access);
+ if (status)
+ {
+ sec_perror("SecKeychainItemSetAccess", status);
+ result = 1;
+ goto loser;
+ }
+ }
+ }
+ }
+ }
+
+loser:
+ if (access)
+ CFRelease(access);
+
+ if (attrList)
+ {
+ status = SecKeychainItemFreeAttributesAndData(attrList, data);
+ if (status)
+ sec_perror("SecKeychainItemFreeAttributesAndData", status);
+ }
+
+ if (info)
+ {
+ status = SecKeychainFreeAttributeInfo(info);
+ if (status)
+ sec_perror("SecKeychainFreeAttributeInfo", status);
+ }
+
+ if (keychain)
+ CFRelease(keychain);
+
+ return result;
+}
+
+static void
+print_buffer_hex(FILE *stream, size_t length, const void *data)
+{
+ uint8 *p = (uint8 *) data;
+ while (length--)
+ {
+ int ch = *p++;
+ fprintf(stream, "%02X", ch);
+ }
+}
+
+static void
+print_buffer_ascii(FILE *stream, size_t length, const void *data)
+{
+ uint8 *p = (uint8 *) data;
+ while (length--)
+ {
+ int ch = *p++;
+ if (ch >= ' ' && ch <= '~' && ch != '\\')
+ {
+ fputc(ch, stream);
+ }
+ else
+ {
+ fputc('\\', stream);
+ fputc('0' + ((ch >> 6) & 7), stream);
+ fputc('0' + ((ch >> 3) & 7), stream);
+ fputc('0' + ((ch >> 0) & 7), stream);
+ }
+ }
+}
+
+void
+print_buffer(FILE *stream, size_t length, const void *data)
+{
+ uint8 *p = (uint8 *) data;
+ Boolean hex = FALSE;
+ Boolean ascii = FALSE;
+ UInt32 ix;
+ for (ix = 0; ix < length; ++ix)
+ {
+ int ch = *p++;
+ if (ch >= ' ' && ch <= '~' && ch != '\\')
+ ascii = TRUE;
+ else
+ hex = TRUE;
+ }
+
+ if (hex)
+ {
+ fputc('0', stream);
+ fputc('x', stream);
+ print_buffer_hex(stream, length, data);
+ if (ascii)
+ fputc(' ', stream);
+ fputc(' ', stream);
+ }
+ if (ascii)
+ {
+ fputc('"', stream);
+ print_buffer_ascii(stream, length, data);
+ fputc('"', stream);
+ }
+}
+
+void
+print_uint32(FILE *stream, uint32 n)
+{
+ n = OSSwapHostToBigInt32 (n);
+ print_buffer(stream, sizeof(UInt32), &n);
+}
+
+// Returns base16 value of input hexadecimal character.
+// If the input character is not valid hex, output error flag is set.
+//
+unsigned char
+hexToValue(char c, char *error)
+{
+ static const char digits[] = "0123456789abcdef";
+ char *p = strchr(digits, tolower(c));
+ if (p) { return p - digits; }
+ if (error) { *error = 1; }
+ return 0;
+}
+
+unsigned char
+hexValue(char c)
+{
+ return hexToValue(c, NULL);
+}
+
+// Returns true if we can convert the supplied hex string to data,
+// with pointer to the allocated data and its length as optional output.
+// This function will zero-pad an odd number of nibbles in hexString,
+// e.g. an input of 'FFF' is treated as 0x0FFF.
+// If outData is supplied, caller must free returned value.
+//
+bool
+convertHex(const char *hexString, uint8_t **outData, size_t *outLength)
+{
+ if (!hexString) { return false; }
+
+ size_t num_nibbles = strlen(hexString);
+ uint8_t zero_pad = ((num_nibbles % 2) != 0) ? 1 : 0;
+ size_t num_bytes = (num_nibbles / 2) + zero_pad;
+ uint8_t *data = calloc(num_bytes, sizeof(*data));
+ char error = 0;
+ if (zero_pad) {
+ data[0] = hexToValue(hexString[0], &error);
+ }
+ for (size_t n = zero_pad; n < num_bytes; n++) {
+ data[n] = hexToValue(hexString[2*n], &error) << 4 | hexToValue(hexString[2*n+1], &error);
+ }
+ if (error) {
+ free(data);
+ return false;
+ }
+ if (outData) {
+ *outData = data;
+ } else {
+ memset(data, 0, num_bytes);
+ free(data);
+ }
+ if (outLength) {
+ *outLength = num_bytes;
+ }
+ return true;
+}
+
+void
+fromHex(const char *hexDigits, CSSM_DATA *data)
+{
+ size_t bytes = strlen(hexDigits) / 2; // (discards malformed odd end)
+ if (bytes > data->Length)
+ return;
+ // length(bytes); // (will assert if we try to grow it)
+ size_t n;
+ for (n = 0; n < bytes; n++) {
+ data->Data[n] = (uint8)(hexValue(hexDigits[2*n]) << 4 | hexValue(hexDigits[2*n+1]));
+ }
+}
+
+CFDataRef CF_RETURNS_RETAINED
+cfFromHex(CFStringRef hex) {
+ // behavior is undefined if you pass in a non-hex string. Don't do that.
+ char* chex;
+ size_t len;
+
+ GetCStringFromCFString(hex, &chex, &len);
+ if(len == 0) {
+ return NULL;
+ }
+
+ size_t bytes = len/2;
+ CFMutableDataRef bin = CFDataCreateMutable(kCFAllocatorDefault, bytes);
+ CFDataIncreaseLength(bin, bytes);
+
+ if(!bin || (size_t) CFDataGetLength(bin) != bytes) {
+ CFReleaseNull(bin);
+ return NULL;
+ }
+
+ UInt8* data = CFDataGetMutableBytePtr(bin);
+ for(size_t i = 0; i < bytes; i++) {
+ data[i] = (uint8)(hexValue(chex[2*i]) << 4 | hexValue(chex[2*i+1]));
+ }
+
+ return bin;
+}
+
+CFStringRef CF_RETURNS_RETAINED cfToHex(CFDataRef bin) {
+ size_t len = CFDataGetLength(bin) * 2;
+ CFMutableStringRef str = CFStringCreateMutable(NULL, len);
+
+ static const char* digits[] = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"};
+
+ const uint8_t* data = CFDataGetBytePtr(bin);
+ for (CFIndex i = 0; i < CFDataGetLength(bin); i++) {
+ CFStringAppendCString(str, digits[data[i] >> 4], 1);
+ CFStringAppendCString(str, digits[data[i] & 0xf], 1);
+ }
+ return str;
+}
+
+void
+safe_CFRelease(void *cfTypeRefPtr)
+{
+ CFTypeRef *obj = (CFTypeRef *)cfTypeRefPtr;
+ if (obj && *obj) {
+ CFRelease(*obj);
+ *obj = NULL;
+ }
+}
+
+
+void
+GetCStringFromCFString(CFStringRef cfstring, char** cstr, size_t* len) {
+ CFIndex strLen = CFStringGetLength(cfstring);
+ CFIndex bufLen = 1 + CFStringGetMaximumSizeForEncoding(strLen, kCFStringEncodingUTF8);
+ *cstr = (char *)malloc(bufLen);
+ if (!CFStringGetCString(cfstring, *cstr, bufLen-1, kCFStringEncodingUTF8)) {
+ (*cstr)[0]=0;
+ }
+ // Handle non-8-bit characters.
+ *len = strnlen(*cstr, strLen);
+}
+
+CFDictionaryRef CF_RETURNS_RETAINED makeCFDictionaryFromData(CFDataRef data)
+{
+ if (data) {
+ CFPropertyListRef plist = CFPropertyListCreateFromXMLData(NULL, data, kCFPropertyListImmutable, NULL);
+ if (plist && CFGetTypeID(plist) != CFDictionaryGetTypeID()) {
+ safe_CFRelease(&plist);
+ return NULL;
+ }
+ return (CFDictionaryRef) plist;
+ } else {
+ return NULL;
+ }
+}
+
+void print_partition_id_list(FILE* stream, CFStringRef description) {
+ CFDataRef binary = NULL;
+ CFDictionaryRef partitionList = NULL;
+ CFArrayRef partitionIDs = NULL;
+
+ if(!description) {
+ goto error;
+ }
+ binary = cfFromHex(description);
+ if(!binary) {
+ goto error;
+ }
+ partitionList = makeCFDictionaryFromData(binary);
+ if(!partitionList) {
+ goto error;
+ }
+
+ partitionIDs = CFDictionaryGetValue(partitionList, CFSTR("Partitions"));
+ if(!partitionIDs) {
+ goto error;
+ }
+
+ for(CFIndex i = 0; i < CFArrayGetCount(partitionIDs); i++) {
+ CFStringRef s = CFArrayGetValueAtIndex(partitionIDs, i);
+ if(!s) {
+ goto error;
+ }
+
+ if(i != 0) {
+ fprintf(stream, ", ");
+ }
+ print_cfstring(stream, s);
+ }
+
+ goto cleanup;
+error:
+ fprintf(stream, "invalid partition ID: ");
+ print_cfstring(stream, description);
+cleanup:
+ // don't release partitionIDs; it's an element of partitionList
+ safe_CFRelease(&binary);
+ safe_CFRelease(&partitionList);
+
+ return;
+}
+
+/*
+ * map a 6-bit binary value to a printable character.
+ */
+static const
+unsigned char bintoasc[] =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+/*
+ * map 6 bits to a printing char
+ */
+#define ENC(c) (bintoasc[((c) & 0x3f)])
+
+#define PAD '='
+
+/*
+ * map one group of up to 3 bytes at inp to 4 bytes at outp.
+ * Count is number of valid bytes in *inp; if less than 3, the
+ * 1 or two extras must be zeros.
+ */
+static void
+encChunk(const unsigned char *inp,
+ unsigned char *outp,
+ size_t count)
+{
+ unsigned char c1, c2, c3, c4;
+
+ c1 = *inp >> 2;
+ c2 = ((inp[0] << 4) & 0x30) | ((inp[1] >> 4) & 0xf);
+ c3 = ((inp[1] << 2) & 0x3c) | ((inp[2] >> 6) & 0x3);
+ c4 = inp[2] & 0x3f;
+ *outp++ = ENC(c1);
+ *outp++ = ENC(c2);
+ if (count == 1) {
+ *outp++ = PAD;
+ *outp = PAD;
+ } else {
+ *outp++ = ENC(c3);
+ if (count == 2) {
+ *outp = PAD;
+ }
+ else {
+ *outp = ENC(c4);
+ }
+ }
+}
+
+static unsigned char *
+malloc_enc64_with_lines(const unsigned char *inbuf,
+ size_t inlen,
+ size_t linelen,
+ unsigned *outlen)
+{
+ unsigned outTextLen;
+ unsigned len; // to malloc, liberal
+ unsigned olen = 0; // actual output size
+ unsigned char *outbuf;
+ unsigned char endbuf[3];
+ unsigned i;
+ unsigned char *outp;
+ unsigned numLines;
+ unsigned thisLine;
+
+ outTextLen = ((((unsigned)inlen) + 2) / 3) * 4;
+ if(linelen) {
+ /*
+ * linelen must be 0 mod 4 for this to work; round up...
+ */
+ if((linelen & 0x03) != 0) {
+ linelen = (linelen + 3) & 0xfffffffc;
+ }
+ numLines = (outTextLen + ((unsigned)linelen) - 1)/ linelen;
+ }
+ else {
+ numLines = 1;
+ }
+
+ /*
+ * Total output size = encoded text size plus one newline per
+ * line of output, plus trailing NULL. We always generate newlines
+ * as \n; when decoding, we tolerate \r\n (Microsoft) or \n.
+ */
+ len = outTextLen + (2 * numLines) + 1;
+ outbuf = (unsigned char*)malloc(len);
+ outp = outbuf;
+ thisLine = 0;
+
+ while(inlen) {
+ if(inlen < 3) {
+ for(i=0; i<3; i++) {
+ if(i < inlen) {
+ endbuf[i] = inbuf[i];
+ }
+ else {
+ endbuf[i] = 0;
+ }
+ }
+ encChunk(endbuf, outp, inlen);
+ inlen = 0;
+ }
+ else {
+ encChunk(inbuf, outp, 3);
+ inlen -= 3;
+ inbuf += 3;
+ }
+ outp += 4;
+ thisLine += 4;
+ olen += 4;
+ if((linelen != 0) && (thisLine >= linelen) && inlen) {
+ /*
+ * last trailing newline added below
+ * Note we don't split 4-byte output chunks over newlines
+ */
+ *outp++ = '\n';
+ olen++;
+ thisLine = 0;
+ }
+ }
+ *outp++ = '\n';
+ olen += 1;
+ *outlen = olen;
+ return outbuf;
+}
+
+void
+print_buffer_pem(FILE *stream, const char *headerString, size_t length, const void *data)
+{
+ unsigned char *buf;
+ unsigned bufLen;
+
+ if (headerString)
+ fprintf(stream, "-----BEGIN %s-----\n", headerString);
+ buf = malloc_enc64_with_lines(data, length, 64, &bufLen);
+ fwrite(buf, bufLen, 1, stream);
+ free(buf);
+ if (headerString)
+ fprintf(stream, "-----END %s-----\n", headerString);
+}
+
+char*
+prompt_password(const char* keychainName) {
+ const char *fmt = "password to unlock %s: ";
+ const char *name = keychainName ? keychainName : "default";
+ char *prompt = malloc(strlen(fmt) + strlen(name));
+ sprintf(prompt, fmt, name);
+ char *password = getpass(prompt);
+ free(prompt);
+ return password;
+}