#include <CoreFoundation/CoreFoundation.h>
#include <Security/SecItem.h>
#include <SecurityTool/tool_errors.h>
#include <SecurityTool/readline.h>
#include <utilities/SecCFWrappers.h>
#include "SecurityCommands.h"
typedef uint32_t SecProtocolType;
typedef uint32_t SecAuthenticationType;
static void
keychain_query_parse_string(CFMutableDictionaryRef q, CFStringRef s) {
bool inkey = true;
bool escaped = false;
CFStringRef key = NULL;
CFMutableStringRef str = CFStringCreateMutable(0, 0);
CFRange rng = { .location = 0, .length = CFStringGetLength(s) };
CFCharacterSetRef cs_key = CFCharacterSetCreateWithCharactersInString(0, CFSTR("=\\"));
CFCharacterSetRef cs_value = CFCharacterSetCreateWithCharactersInString(0, CFSTR(",\\"));
while (rng.length) {
CFRange r;
CFStringRef sub;
bool complete = false;
if (escaped) {
r.location = rng.location;
r.length = 1;
sub = CFStringCreateWithSubstring(0, s, r);
escaped = false;
} else if (CFStringFindCharacterFromSet(s, inkey ? cs_key : cs_value, rng, 0, &r)) {
if (CFStringGetCharacterAtIndex(s, r.location) == '\\') {
escaped = true;
} else {
complete = true;
}
CFIndex next = r.location + 1;
r.length = r.location - rng.location;
r.location = rng.location;
sub = CFStringCreateWithSubstring(0, s, r);
rng.length -= next - rng.location;
rng.location = next;
} else {
sub = CFStringCreateWithSubstring(0, s, rng);
rng.location += rng.length;
rng.length = 0;
complete = true;
}
CFStringAppend(str, sub);
CFRelease(sub);
if (complete) {
CFStringRef value = CFStringCreateCopy(0, str);
CFStringReplaceAll(str, CFSTR(""));
if (inkey) {
key = value;
} else {
CFDictionarySetValue(q, key, value);
CFReleaseNull(value);
CFReleaseNull(key);
}
inkey = !inkey;
}
}
if (key) {
CFDictionarySetValue(q, key, kCFBooleanTrue);
CFRelease(key);
}
CFRelease(str);
CFReleaseSafe(cs_key);
CFReleaseSafe(cs_value);
}
static void
keychain_query_parse_cstring(CFMutableDictionaryRef q, const char *query) {
CFStringRef s;
s = CFStringCreateWithCStringNoCopy(0, query, kCFStringEncodingUTF8, kCFAllocatorNull);
keychain_query_parse_string(q, s);
CFRelease(s);
}
static CFMutableDictionaryRef
keychain_create_query_from_string(const char *query) {
CFMutableDictionaryRef q;
q = CFDictionaryCreateMutable(0, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
keychain_query_parse_cstring(q, query);
return q;
}
static void add_key(const void *key, const void *value, void *context) {
CFArrayAppendValue(context, key);
}
static void display_item(const void *v_item, void *context) {
CFDictionaryRef item = (CFDictionaryRef)v_item;
CFIndex dict_count, key_ix, key_count;
CFMutableArrayRef keys = NULL;
CFIndex maxWidth = 10;
dict_count = CFDictionaryGetCount(item);
keys = CFArrayCreateMutable(kCFAllocatorDefault, dict_count,
&kCFTypeArrayCallBacks);
CFDictionaryApplyFunction(item, add_key, keys);
key_count = CFArrayGetCount(keys);
CFArraySortValues(keys, CFRangeMake(0, key_count),
(CFComparatorFunction)CFStringCompare, 0);
for (key_ix = 0; key_ix < key_count; ++key_ix) {
CFStringRef key = (CFStringRef)CFArrayGetValueAtIndex(keys, key_ix);
CFTypeRef value = CFDictionaryGetValue(item, key);
CFMutableStringRef line = CFStringCreateMutable(NULL, 0);
CFStringAppend(line, key);
CFIndex jx;
for (jx = CFStringGetLength(key);
jx < maxWidth; ++jx) {
CFStringAppend(line, CFSTR(" "));
}
CFStringAppend(line, CFSTR(" : "));
if (CFStringGetTypeID() == CFGetTypeID(value)) {
CFStringAppend(line, (CFStringRef)value);
} else if (CFNumberGetTypeID() == CFGetTypeID(value)) {
CFNumberRef v_n = (CFNumberRef)value;
CFStringAppendFormat(line, NULL, CFSTR("%@"), v_n);
} else if (CFDateGetTypeID() == CFGetTypeID(value)) {
CFDateRef v_d = (CFDateRef)value;
CFStringAppendFormat(line, NULL, CFSTR("%@"), v_d);
} else if (CFDataGetTypeID() == CFGetTypeID(value)) {
CFDataRef v_d = (CFDataRef)value;
CFStringRef v_s = CFStringCreateFromExternalRepresentation(
kCFAllocatorDefault, v_d, kCFStringEncodingUTF8);
if (v_s) {
CFStringAppend(line, CFSTR("/"));
CFStringAppend(line, v_s);
CFStringAppend(line, CFSTR("/ "));
CFRelease(v_s);
}
const uint8_t *bytes = CFDataGetBytePtr(v_d);
CFIndex len = CFDataGetLength(v_d);
for (jx = 0; jx < len; ++jx) {
CFStringAppendFormat(line, NULL, CFSTR("%.02X"), bytes[jx]);
}
} else {
CFStringAppendFormat(line, NULL, CFSTR("%@"), value);
}
CFStringWriteToFileWithNewline(line, stdout);
CFRelease(line);
}
CFRelease(keys);
CFStringWriteToFileWithNewline(CFSTR("===="), stdout);
}
static void display_results(CFTypeRef results) {
if (CFGetTypeID(results) == CFArrayGetTypeID()) {
CFArrayRef r_a = (CFArrayRef)results;
CFArrayApplyFunction(r_a, CFRangeMake(0, CFArrayGetCount(r_a)),
display_item, NULL);
} else if (CFGetTypeID(results) == CFArrayGetTypeID()) {
display_item(results, NULL);
} else {
fprintf(stderr, "SecItemCopyMatching returned unexpected results:");
CFShow(results);
}
}
static OSStatus do_find_or_delete(CFDictionaryRef query, bool do_delete) {
OSStatus result;
if (do_delete) {
result = SecItemDelete(query);
if (result) {
sec_perror("SecItemDelete", result);
}
} else {
CFTypeRef results = NULL;
result = SecItemCopyMatching(query, &results);
if (result) {
sec_perror("SecItemCopyMatching", result);
} else {
display_results(results);
}
CFReleaseSafe(results);
}
return result;
}
static int
do_keychain_find_or_delete_internet_password(Boolean do_delete,
const char *serverName, const char *securityDomain,
const char *accountName, const char *path, UInt16 port,
SecProtocolType protocol, SecAuthenticationType authenticationType,
Boolean get_password)
{
OSStatus result;
CFDictionaryRef query = NULL;
const void *keys[11], *values[11];
CFIndex ix = 0;
keys[ix] = kSecClass;
values[ix++] = kSecClassInternetPassword;
if (serverName) {
keys[ix] = kSecAttrServer;
values[ix++] = CFStringCreateWithCStringNoCopy(NULL, serverName,
kCFStringEncodingUTF8, kCFAllocatorNull);
}
if (securityDomain) {
keys[ix] = kSecAttrSecurityDomain;
values[ix++] = CFStringCreateWithCStringNoCopy(NULL, securityDomain,
kCFStringEncodingUTF8, kCFAllocatorNull);
}
if (accountName) {
keys[ix] = kSecAttrAccount;
values[ix++] = CFStringCreateWithCStringNoCopy(NULL, accountName,
kCFStringEncodingUTF8, kCFAllocatorNull);
}
if (path) {
keys[ix] = kSecAttrPath;
values[ix++] = CFStringCreateWithCStringNoCopy(NULL, path,
kCFStringEncodingUTF8, kCFAllocatorNull);
}
if (port != 0) {
keys[ix] = kSecAttrPort;
values[ix++] = CFNumberCreate(NULL, kCFNumberSInt16Type, &port);
}
if (protocol != 0) {
keys[ix] = kSecAttrProtocol;
values[ix++] = CFNumberCreate(NULL, kCFNumberSInt32Type, &protocol);
}
if (authenticationType != 0) {
keys[ix] = kSecAttrAuthenticationType;
values[ix++] = CFNumberCreate(NULL, kCFNumberSInt32Type,
&authenticationType);
}
if (get_password) {
keys[ix] = kSecReturnData;
values[ix++] = kCFBooleanTrue;
}
keys[ix] = kSecReturnAttributes;
values[ix++] = kCFBooleanTrue;
if (!do_delete) {
keys[ix] = kSecMatchLimit;
values[ix++] = kSecMatchLimitAll;
}
query = CFDictionaryCreate(NULL, keys, values, ix,
&kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
result = do_find_or_delete(query, do_delete);
CFReleaseSafe(query);
return result;
}
static int
parse_fourcharcode(const char *name, uint32_t *code)
{
char *p = (char *)code;
strncpy(p, name, 4);
return 0;
}
static int
keychain_find_or_delete_internet_password(Boolean do_delete, int argc, char * const *argv)
{
char *serverName = NULL, *securityDomain = NULL, *accountName = NULL, *path = NULL;
UInt16 port = 0;
SecProtocolType protocol = 0;
SecAuthenticationType authenticationType = 0;
int ch, result = 0;
Boolean get_password = FALSE;
while ((ch = getopt(argc, argv, "a:d:hgp:P:r:s:t:")) != -1)
{
switch (ch)
{
case 'a':
accountName = optarg;
break;
case 'd':
securityDomain = optarg;
break;
case 'g':
if (do_delete)
return 2;
get_password = TRUE;
break;
case 'p':
path = optarg;
break;
case 'P':
port = atoi(optarg);
break;
case 'r':
result = parse_fourcharcode(optarg, &protocol);
if (result)
goto loser;
break;
case 's':
serverName = optarg;
break;
case 't':
result = parse_fourcharcode(optarg, &authenticationType);
if (result)
goto loser;
break;
case '?':
default:
return 2;
}
}
argc -= optind;
argv += optind;
result = do_keychain_find_or_delete_internet_password(do_delete, serverName, securityDomain,
accountName, path, port, protocol,authenticationType, get_password);
loser:
return result;
}
int
keychain_find_internet_password(int argc, char * const *argv) {
return keychain_find_or_delete_internet_password(0, argc, argv);
}
int
keychain_delete_internet_password(int argc, char * const *argv) {
return keychain_find_or_delete_internet_password(1, argc, argv);
}
static int
do_keychain_find_or_delete_generic_password(Boolean do_delete,
const char *serviceName, const char *accountName,
Boolean get_password)
{
OSStatus result;
CFDictionaryRef query = NULL;
const void *keys[6], *values[6];
CFIndex ix = 0;
keys[ix] = kSecClass;
values[ix++] = kSecClassGenericPassword;
if (serviceName) {
keys[ix] = kSecAttrService;
values[ix++] = CFStringCreateWithCStringNoCopy(NULL, serviceName,
kCFStringEncodingUTF8, kCFAllocatorNull);
}
if (accountName) {
keys[ix] = kSecAttrAccount;
values[ix++] = CFStringCreateWithCStringNoCopy(NULL, accountName,
kCFStringEncodingUTF8, kCFAllocatorNull);
}
if (get_password) {
keys[ix] = kSecReturnData;
values[ix++] = kCFBooleanTrue;
}
keys[ix] = kSecReturnAttributes;
values[ix++] = kCFBooleanTrue;
if (!do_delete) {
keys[ix] = kSecMatchLimit;
values[ix++] = kSecMatchLimitAll;
}
query = CFDictionaryCreate(NULL, keys, values, ix,
&kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
result = do_find_or_delete(query, do_delete);
CFReleaseSafe(query);
return result;
}
int keychain_item(int argc, char * const *argv) {
int ch, result = 0;
CFMutableDictionaryRef query, update = NULL;
bool get_password = false;
bool do_delete = false;
bool do_add = false;
bool verbose = false;
int limit = 0;
query = CFDictionaryCreateMutable(0, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
while ((ch = getopt(argc, argv, "ad:Df:gq:u:vl:")) != -1)
{
switch (ch)
{
case 'a':
do_add = true;
break;
case 'D':
do_delete = true;
break;
case 'd':
{
CFStringRef data = CFStringCreateWithCString(0, optarg, kCFStringEncodingUTF8);
if (data) {
CFDictionarySetValue(update ? update : query, kSecValueData, data);
CFRelease(data);
} else {
result = 1;
goto out;
}
break;
}
case 'f':
{
CFDataRef data = copyFileContents(optarg);
CFDictionarySetValue(update ? update : query, kSecValueData, data);
CFRelease(data);
break;
}
case 'g':
get_password = true;
break;
case 'q':
keychain_query_parse_cstring(query, optarg);
break;
case 'u':
if (!update)
update = keychain_create_query_from_string(optarg);
else
keychain_query_parse_cstring(update, optarg);
break;
case 'v':
verbose = true;
break;
case 'l':
limit = atoi(optarg);
break;
case '?':
default:
result = 2;
goto out;
}
}
if (((do_add || do_delete) && (get_password || update)) || !query) {
result = 2;
goto out;
}
argc -= optind;
argv += optind;
int ix;
for (ix = 0; ix < argc; ++ix) {
keychain_query_parse_cstring(query, argv[ix]);
}
if (!update && !do_add && !do_delete) {
CFDictionarySetValue(query, kSecReturnAttributes, kCFBooleanTrue);
if(limit) {
CFNumberRef cfLimit = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &limit);
CFDictionarySetValue(query, kSecMatchLimit, cfLimit);
CFReleaseSafe(cfLimit);
} else {
CFDictionarySetValue(query, kSecMatchLimit, kSecMatchLimitAll);
}
if (get_password)
CFDictionarySetValue(query, kSecReturnData, kCFBooleanTrue);
}
if (verbose)
CFShow(query);
OSStatus error;
if (do_add) {
error = SecItemAdd(query, NULL);
if (error) {
sec_perror("SecItemAdd", error);
result = 1;
}
} else if (update) {
error = SecItemUpdate(query, update);
if (error) {
sec_perror("SecItemUpdate", error);
result = 1;
}
} else if (do_delete) {
error = SecItemDelete(query);
if (error) {
sec_perror("SecItemDelete", error);
result = 1;
}
} else {
do_find_or_delete(query, do_delete);
}
out:
CFReleaseSafe(query);
CFReleaseSafe(update);
return result;
}
static int
keychain_find_or_delete_generic_password(Boolean do_delete,
int argc, char * const *argv)
{
char *serviceName = NULL, *accountName = NULL;
int ch, result = 0;
Boolean get_password = FALSE;
while ((ch = getopt(argc, argv, "a:s:g")) != -1)
{
switch (ch)
{
case 'a':
accountName = optarg;
break;
case 'g':
if (do_delete)
return 2;
get_password = TRUE;
break;
case 's':
serviceName = optarg;
break;
case '?':
default:
return 2;
}
}
argc -= optind;
argv += optind;
result = do_keychain_find_or_delete_generic_password(do_delete,
serviceName, accountName, get_password);
return result;
}
int
keychain_find_generic_password(int argc, char * const *argv) {
return keychain_find_or_delete_generic_password(0, argc, argv);
}
int
keychain_delete_generic_password(int argc, char * const *argv) {
return keychain_find_or_delete_generic_password(1, argc, argv);
}