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