1//
2//
3//
4//
5
6
7
8#include <CoreFoundation/CoreFoundation.h>
9
10#include <Security/SecItem.h>
11#include <Security/SecItemPriv.h>
12
13#include <SecurityTool/tool_errors.h>
14#include <SecurityTool/readline.h>
15
16#include <utilities/SecCFWrappers.h>
17
18#include "SecurityCommands.h"
19
20#include "keychain_util.h"
21#include <Security/SecAccessControl.h>
22#include <Security/SecAccessControlPriv.h>
23
24//
25// Craptastic hacks.
26
27typedef uint32_t SecProtocolType;
28typedef uint32_t SecAuthenticationType;
29
30
31static CFMutableDictionaryRef
32keychain_create_query_from_string(const char *query) {
33    CFMutableDictionaryRef q;
34
35    q = CFDictionaryCreateMutable(0, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
36    if (!keychain_query_parse_cstring(q, query)) {
37        CFReleaseNull(q);
38    }
39    return q;
40}
41
42static void add_key(const void *key, const void *value, void *context) {
43    CFArrayAppendValue(context, key);
44}
45
46static void display_item(const void *v_item, void *context) {
47    CFDictionaryRef item = (CFDictionaryRef)v_item;
48    CFIndex dict_count, key_ix, key_count;
49    CFMutableArrayRef keys = NULL;
50    CFIndex maxWidth = 10; /* Maybe precompute this or grab from context? */
51
52    dict_count = CFDictionaryGetCount(item);
53    keys = CFArrayCreateMutable(kCFAllocatorDefault, dict_count,
54        &kCFTypeArrayCallBacks);
55    CFDictionaryApplyFunction(item, add_key, keys);
56    key_count = CFArrayGetCount(keys);
57    CFArraySortValues(keys, CFRangeMake(0, key_count),
58        (CFComparatorFunction)CFStringCompare, 0);
59
60    for (key_ix = 0; key_ix < key_count; ++key_ix) {
61        CFStringRef key = (CFStringRef)CFArrayGetValueAtIndex(keys, key_ix);
62        CFTypeRef value = CFDictionaryGetValue(item, key);
63        CFMutableStringRef line = CFStringCreateMutable(NULL, 0);
64
65        CFStringAppend(line, key);
66        CFIndex jx;
67        for (jx = CFStringGetLength(key);
68            jx < maxWidth; ++jx) {
69            CFStringAppend(line, CFSTR(" "));
70        }
71        CFStringAppend(line, CFSTR(" : "));
72        if (CFStringGetTypeID() == CFGetTypeID(value)) {
73            CFStringAppend(line, (CFStringRef)value);
74        } else if (CFNumberGetTypeID() == CFGetTypeID(value)) {
75            CFNumberRef v_n = (CFNumberRef)value;
76            CFStringAppendFormat(line, NULL, CFSTR("%@"), v_n);
77        } else if (CFDateGetTypeID() == CFGetTypeID(value)) {
78            CFDateRef v_d = (CFDateRef)value;
79            CFStringAppendFormat(line, NULL, CFSTR("%@"), v_d);
80        } else if (CFDataGetTypeID() == CFGetTypeID(value)) {
81            CFDataRef v_d = (CFDataRef)value;
82            CFStringRef v_s = CFStringCreateFromExternalRepresentation(
83                kCFAllocatorDefault, v_d, kCFStringEncodingUTF8);
84            if (v_s) {
85                CFStringAppend(line, CFSTR("/"));
86                CFStringAppend(line, v_s);
87                CFStringAppend(line, CFSTR("/ "));
88                CFRelease(v_s);
89            }
90            const uint8_t *bytes = CFDataGetBytePtr(v_d);
91            CFIndex len = CFDataGetLength(v_d);
92            for (jx = 0; jx < len; ++jx) {
93                CFStringAppendFormat(line, NULL, CFSTR("%.02X"), bytes[jx]);
94            }
95        } else if (SecAccessControlGetTypeID() == CFGetTypeID(value)) {
96            display_sac_line((SecAccessControlRef)value, line);
97        } else {
98            CFStringAppendFormat(line, NULL, CFSTR("%@"), value);
99        }
100
101        CFStringWriteToFileWithNewline(line, stdout);
102
103		CFRelease(line);
104    }
105    CFRelease(keys);
106
107    CFStringWriteToFileWithNewline(CFSTR("===="), stdout);
108
109    //CFShow(item);
110}
111
112
113static void display_results(CFTypeRef results) {
114    if (CFGetTypeID(results) == CFArrayGetTypeID()) {
115        CFArrayRef r_a = (CFArrayRef)results;
116        CFArrayApplyFunction(r_a, CFRangeMake(0, CFArrayGetCount(r_a)),
117            display_item, NULL);
118    } else if (CFGetTypeID(results) == CFArrayGetTypeID()) {
119        display_item(results, NULL);
120    } else {
121        fprintf(stderr, "SecItemCopyMatching returned unexpected results:");
122        CFShow(results);
123    }
124}
125
126static OSStatus do_find_or_delete(CFDictionaryRef query, bool do_delete) {
127    OSStatus result;
128    if (do_delete) {
129        result = SecItemDelete(query);
130        if (result) {
131            sec_perror("SecItemDelete", result);
132        }
133    } else {
134        CFTypeRef results = NULL;
135        result = SecItemCopyMatching(query, &results);
136        if (result) {
137            sec_perror("SecItemCopyMatching", result);
138        } else {
139            display_results(results);
140        }
141        CFReleaseSafe(results);
142    }
143    return result;
144}
145
146static int
147do_keychain_find_or_delete_internet_password(Boolean do_delete,
148	const char *serverName, const char *securityDomain,
149	const char *accountName, const char *path, UInt16 port,
150	SecProtocolType protocol, SecAuthenticationType authenticationType,
151	Boolean get_password)
152 {
153	OSStatus result;
154    CFDictionaryRef query = NULL;
155    const void *keys[11], *values[11];
156    CFIndex ix = 0;
157
158    keys[ix] = kSecClass;
159    values[ix++] = kSecClassInternetPassword;
160	if (serverName) {
161		keys[ix] = kSecAttrServer;
162		values[ix++] = CFStringCreateWithCStringNoCopy(NULL, serverName,
163			kCFStringEncodingUTF8, kCFAllocatorNull);
164	}
165	if (securityDomain) {
166		keys[ix] = kSecAttrSecurityDomain;
167		values[ix++] = CFStringCreateWithCStringNoCopy(NULL, securityDomain,
168			kCFStringEncodingUTF8, kCFAllocatorNull);
169	}
170	if (accountName) {
171		keys[ix] = kSecAttrAccount;
172		values[ix++] = CFStringCreateWithCStringNoCopy(NULL, accountName,
173			kCFStringEncodingUTF8, kCFAllocatorNull);
174	}
175	if (path) {
176		keys[ix] = kSecAttrPath;
177		values[ix++] = CFStringCreateWithCStringNoCopy(NULL, path,
178			kCFStringEncodingUTF8, kCFAllocatorNull);
179	}
180	if (port != 0) {
181		keys[ix] = kSecAttrPort;
182		values[ix++] = CFNumberCreate(NULL, kCFNumberSInt16Type, &port);
183	}
184	if (protocol != 0) {
185		/* Protocol is a 4 char code, perhaps we should use a string rep
186		   instead. */
187		keys[ix] = kSecAttrProtocol;
188		values[ix++] = CFNumberCreate(NULL, kCFNumberSInt32Type, &protocol);
189	}
190	if (authenticationType != 0) {
191		keys[ix] = kSecAttrAuthenticationType;
192		values[ix++] = CFNumberCreate(NULL, kCFNumberSInt32Type,
193			&authenticationType);
194	}
195    if (get_password) {
196        /* Only ask for the data if so required. */
197		keys[ix] = kSecReturnData;
198		values[ix++] = kCFBooleanTrue;
199    }
200    keys[ix] = kSecReturnAttributes;
201    values[ix++] = kCFBooleanTrue;
202	if (!do_delete) {
203		/* If we aren't deleting ask for all items. */
204		keys[ix] = kSecMatchLimit;
205		values[ix++] = kSecMatchLimitAll;
206	}
207
208    query = CFDictionaryCreate(NULL, keys, values, ix,
209        &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
210    result = do_find_or_delete(query, do_delete);
211    CFReleaseSafe(query);
212
213	return result;
214}
215
216static int
217parse_fourcharcode(const char *name, uint32_t *code)
218{
219	/* @@@ Check for errors. */
220	char *p = (char *)code;
221	strncpy(p, name, 4);
222	return 0;
223}
224
225static int
226keychain_find_or_delete_internet_password(Boolean do_delete, int argc, char * const *argv)
227{
228	char *serverName = NULL, *securityDomain = NULL, *accountName = NULL, *path = NULL;
229    UInt16 port = 0;
230    SecProtocolType protocol = 0;
231    SecAuthenticationType authenticationType = 0;
232	int ch, result = 0;
233	Boolean get_password = FALSE;
234
235	while ((ch = getopt(argc, argv, "a:d:hgp:P:r:s:t:")) != -1)
236	{
237		switch  (ch)
238		{
239        case 'a':
240            accountName = optarg;
241            break;
242        case 'd':
243            securityDomain = optarg;
244			break;
245		case 'g':
246            if (do_delete)
247                return 2;
248			get_password = TRUE;
249			break;
250        case 'p':
251            path = optarg;
252            break;
253        case 'P':
254            port = atoi(optarg);
255            break;
256        case 'r':
257			result = parse_fourcharcode(optarg, &protocol);
258			if (result)
259				goto loser;
260			break;
261		case 's':
262			serverName = optarg;
263			break;
264        case 't':
265			result = parse_fourcharcode(optarg, &authenticationType);
266			if (result)
267				goto loser;
268			break;
269        case '?':
270		default:
271			return 2; /* @@@ Return 2 triggers usage message. */
272		}
273	}
274
275	result = do_keychain_find_or_delete_internet_password(do_delete, serverName, securityDomain,
276		accountName, path, port, protocol,authenticationType, get_password);
277
278
279loser:
280
281	return result;
282}
283
284int
285keychain_find_internet_password(int argc, char * const *argv) {
286    return keychain_find_or_delete_internet_password(0, argc, argv);
287}
288
289int
290keychain_delete_internet_password(int argc, char * const *argv) {
291    return keychain_find_or_delete_internet_password(1, argc, argv);
292}
293
294static int
295do_keychain_find_or_delete_generic_password(Boolean do_delete,
296	const char *serviceName, const char *accountName,
297	Boolean get_password)
298 {
299	OSStatus result;
300    CFDictionaryRef query = NULL;
301    const void *keys[6], *values[6];
302    CFIndex ix = 0;
303
304    keys[ix] = kSecClass;
305    values[ix++] = kSecClassGenericPassword;
306	if (serviceName) {
307		keys[ix] = kSecAttrService;
308		values[ix++] = CFStringCreateWithCStringNoCopy(NULL, serviceName,
309			kCFStringEncodingUTF8, kCFAllocatorNull);
310	}
311	if (accountName) {
312		keys[ix] = kSecAttrAccount;
313		values[ix++] = CFStringCreateWithCStringNoCopy(NULL, accountName,
314			kCFStringEncodingUTF8, kCFAllocatorNull);
315	}
316    if (get_password) {
317        /* Only ask for the data if so required. */
318		keys[ix] = kSecReturnData;
319		values[ix++] = kCFBooleanTrue;
320    }
321    keys[ix] = kSecReturnAttributes;
322    values[ix++] = kCFBooleanTrue;
323	if (!do_delete) {
324		/* If we aren't deleting ask for all items. */
325		keys[ix] = kSecMatchLimit;
326		values[ix++] = kSecMatchLimitAll;
327	}
328
329    query = CFDictionaryCreate(NULL, keys, values, ix,
330        &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
331
332    result = do_find_or_delete(query, do_delete);
333
334	CFReleaseSafe(query);
335
336	return result;
337}
338
339int keychain_item(int argc, char * const *argv) {
340	int ch, result = 0;
341    CFMutableDictionaryRef query, update = NULL;
342	bool get_password = false;
343    bool do_delete = false;
344    bool do_add = false;
345    bool verbose = false;
346    int limit = 0;
347
348    query = CFDictionaryCreateMutable(0, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
349	while ((ch = getopt(argc, argv, "ad:Df:gq:u:vl:")) != -1)
350	{
351		switch  (ch)
352		{
353            case 'a':
354                do_add = true;
355                break;
356            case 'D':
357                do_delete = true;
358                break;
359            case 'd':
360            {
361                CFStringRef data = CFStringCreateWithCString(0, optarg, kCFStringEncodingUTF8);
362                if (data) {
363                    CFDictionarySetValue(update ? update : query, kSecValueData, data);
364                    CFRelease(data);
365                } else {
366                    result = 1;
367                    goto out;
368                }
369                break;
370            }
371            case 'f':
372            {
373                CFDataRef data = copyFileContents(optarg);
374                CFDictionarySetValue(update ? update : query, kSecValueData, data);
375                CFRelease(data);
376                break;
377            }
378            case 'g':
379                get_password = true;
380                break;
381            case 'q':
382                if (!keychain_query_parse_cstring(query, optarg)) {
383                result = 1;
384                goto out;
385            }
386                break;
387            case 'u':
388            {
389                bool success = true;
390                if (!update)
391                    update = keychain_create_query_from_string(optarg);
392                else
393                    success = keychain_query_parse_cstring(update, optarg);
394                if (update == NULL || !success) {
395                    result = 1;
396                    goto out;
397                }
398            }
399                break;
400            case 'v':
401                verbose = true;
402                break;
403            case 'l':
404                limit = atoi(optarg);
405                break;
406            case '?':
407            default:
408                /* Return 2 triggers usage message. */
409                result = 2;
410                goto out;
411		}
412	}
413
414    if (((do_add || do_delete) && (get_password || update)) || !query) {
415        result = 2;
416        goto out;
417    }
418
419	argc -= optind;
420	argv += optind;
421
422    int ix;
423    for (ix = 0; ix < argc; ++ix) {
424        if (!keychain_query_parse_cstring(query, argv[ix])) {
425            result = 1;
426            goto out;
427        }
428    }
429
430    if (!update && !do_add && !do_delete) {
431        CFDictionarySetValue(query, kSecReturnAttributes, kCFBooleanTrue);
432        if(limit) {
433            CFNumberRef cfLimit = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &limit);
434            CFDictionarySetValue(query, kSecMatchLimit, cfLimit);
435            CFReleaseSafe(cfLimit);
436        } else {
437            CFDictionarySetValue(query, kSecMatchLimit, kSecMatchLimitAll);
438        }
439        if (get_password)
440            CFDictionarySetValue(query, kSecReturnData, kCFBooleanTrue);
441    }
442
443    if (verbose)
444        CFShow(query);
445
446    OSStatus error;
447    if (do_add) {
448        error = SecItemAdd(query, NULL);
449        if (error) {
450            sec_perror("SecItemAdd", error);
451            result = 1;
452        }
453    } else if (update) {
454        error = SecItemUpdate(query, update);
455        if (error) {
456            sec_perror("SecItemUpdate", error);
457            result = 1;
458        }
459    } else if (do_delete) {
460        error = SecItemDelete(query);
461        if (error) {
462            sec_perror("SecItemDelete", error);
463            result = 1;
464        }
465    } else {
466        do_find_or_delete(query, do_delete);
467    }
468
469out:
470    CFReleaseSafe(query);
471    CFReleaseSafe(update);
472	return result;
473}
474
475static int
476keychain_find_or_delete_generic_password(Boolean do_delete,
477	int argc, char * const *argv)
478{
479	char *serviceName = NULL, *accountName = NULL;
480	int ch, result = 0;
481	Boolean get_password = FALSE;
482
483	while ((ch = getopt(argc, argv, "a:s:g")) != -1)
484	{
485		switch  (ch)
486		{
487        case 'a':
488            accountName = optarg;
489            break;
490        case 'g':
491            if (do_delete)
492                return 2;
493			get_password = TRUE;
494			break;
495		case 's':
496			serviceName = optarg;
497			break;
498        case '?':
499		default:
500			return 2; /* @@@ Return 2 triggers usage message. */
501		}
502	}
503
504	result = do_keychain_find_or_delete_generic_password(do_delete,
505		serviceName, accountName, get_password);
506
507	return result;
508}
509
510int
511keychain_find_generic_password(int argc, char * const *argv) {
512    return keychain_find_or_delete_generic_password(0, argc, argv);
513}
514
515int
516keychain_delete_generic_password(int argc, char * const *argv) {
517    return keychain_find_or_delete_generic_password(1, argc, argv);
518}
519
520#if TARGET_OS_EMBEDDED
521
522int
523keychain_roll_keys(int argc, char * const *argv) {
524	int ch, result = 0;
525    bool force = false;
526
527	while ((ch = getopt(argc, argv, "f")) != -1)
528	{
529		switch  (ch)
530		{
531            case 'f':
532                force = true;
533                break;
534            default:
535                return 2;
536        }
537    }
538    // argc -= optind;
539    // argv += optind;
540
541    (void) argc; // These are set so folks could use them
542    (void) argv; // silence the analyzer since they're not used
543
544    CFErrorRef error = NULL;
545    bool ok = _SecKeychainRollKeys(force, &error);
546
547    fprintf(stderr, "Keychain keys up to date: %s\n", ok ? "yes" : "no");
548    if (!ok && error) {
549        result = (int)CFErrorGetCode(error);
550        CFShow(error);
551    }
552
553    return result;
554}
555
556#endif
557