X-Git-Url: https://git.saurik.com/apple/security.git/blobdiff_plain/84aacf34eae6543be9f0280b2015385f91e5c2c6..b54c578e17e9bcbd74aa30ea75e25e955b9a6205:/SecurityTool/macOS/keychain_add.c?ds=inline diff --git a/SecurityTool/macOS/keychain_add.c b/SecurityTool/macOS/keychain_add.c new file mode 100644 index 00000000..506ea3a0 --- /dev/null +++ b/SecurityTool/macOS/keychain_add.c @@ -0,0 +1,1018 @@ +/* + * Copyright (c) 2003-2009,2011-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_add.c + */ + +#include "keychain_add.h" +#include "keychain_find.h" +#include "readline_cssm.h" +#include "security_tool.h" +#include "access_utils.h" +#include "keychain_utilities.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// SecTrustedApplicationCreateApplicationGroup +#include + + +static int +do_update_generic_password(const char *keychainName, + FourCharCode itemCreator, + FourCharCode itemType, + const char *kind, + const char *value, + const char *comment, + const char *label, + const char *serviceName, + const char *accountName, + const void *passwordData, + uint32_t passwordLength, + SecAccessRef access) +{ + OSStatus status; + SecKeychainRef keychainRef = NULL; + SecKeychainItemRef itemRef = NULL; + + if (keychainName) { + keychainRef = keychain_open(keychainName); + } + itemRef = find_first_generic_password(keychainRef,itemCreator,itemType,kind,value,comment,label,serviceName,accountName); + if (keychainRef) { + CFRelease(keychainRef); + } + if (!itemRef) { + return errSecItemNotFound; + } + + // build list of attributes + SecKeychainAttribute attrs[8]; // maximum number of attributes + SecKeychainAttributeList attrList = { 0, attrs }; + + if ((UInt32)itemCreator != 0) { + attrs[attrList.count].tag = kSecCreatorItemAttr; + attrs[attrList.count].length = sizeof(FourCharCode); + attrs[attrList.count].data = (FourCharCode *)&itemCreator; + attrList.count++; + } + if ((UInt32)itemType != 0) { + attrs[attrList.count].tag = kSecTypeItemAttr; + attrs[attrList.count].length = sizeof(FourCharCode); + attrs[attrList.count].data = (FourCharCode *)&itemType; + attrList.count++; + } + if (kind != NULL) { + attrs[attrList.count].tag = kSecDescriptionItemAttr; + attrs[attrList.count].length = (UInt32) strlen(kind); + attrs[attrList.count].data = (void *)kind; + attrList.count++; + } + if (value != NULL) { + attrs[attrList.count].tag = kSecGenericItemAttr; + attrs[attrList.count].length = (UInt32) strlen(value); + attrs[attrList.count].data = (void *)value; + attrList.count++; + } + if (comment != NULL) { + attrs[attrList.count].tag = kSecCommentItemAttr; + attrs[attrList.count].length = (UInt32) strlen(comment); + attrs[attrList.count].data = (void *)comment; + attrList.count++; + } + if (label != NULL) { + attrs[attrList.count].tag = kSecLabelItemAttr; + attrs[attrList.count].length = (UInt32) strlen(label); + attrs[attrList.count].data = (void *)label; + attrList.count++; + } + if (serviceName != NULL) { + attrs[attrList.count].tag = kSecServiceItemAttr; + attrs[attrList.count].length = (UInt32) strlen(serviceName); + attrs[attrList.count].data = (void *)serviceName; + attrList.count++; + } + if (accountName != NULL) { + attrs[attrList.count].tag = kSecAccountItemAttr; + attrs[attrList.count].length = (UInt32) strlen(accountName); + attrs[attrList.count].data = (void *)accountName; + attrList.count++; + } + + // modify attributes and password data, if provided + status = SecKeychainItemModifyContent(itemRef, &attrList, passwordLength, passwordData); + if (status) { + sec_error("SecKeychainItemModifyContent: %s", sec_errstr(status)); + } + + // modify access, if provided + if (!status && access) { + SecAccessRef curAccess = NULL; + // for historical reasons, we have to modify the item's existing access reference + // (setting the item's access to a freshly created SecAccessRef instance doesn't behave as expected) + status = SecKeychainItemCopyAccess(itemRef, &curAccess); + if (status) { + sec_error("SecKeychainItemCopyAccess: %s", sec_errstr(status)); + } else { + int result = merge_access(curAccess, access); // make changes to the existing access reference + if (result == noErr) { + status = SecKeychainItemSetAccess(itemRef, curAccess); // will prompt + if (status) { + sec_error("SecKeychainItemSetAccess: %s", sec_errstr(status)); + } + } + } + if (curAccess) { + CFRelease(curAccess); + } + } + + CFRelease(itemRef); + + return status; +} + +static int +do_add_generic_password(const char *keychainName, + FourCharCode itemCreator, + FourCharCode itemType, + const char *kind, + const char *value, + const char *comment, + const char *label, + const char *serviceName, + const char *accountName, + const void *passwordData, + uint32_t passwordLength, + SecAccessRef access, + Boolean update) +{ + SecKeychainRef keychain = NULL; + OSStatus result = 0; + SecKeychainItemRef itemRef = NULL; + + // if update flag is specified, try to find and update an existing item + if (update) { + result = do_update_generic_password(keychainName,itemCreator,itemType,kind,value, + comment,label,serviceName,accountName,passwordData,passwordLength,access); + if (result == noErr) + return result; + } + + if (keychainName) + { + keychain = keychain_open(keychainName); + if (!keychain) + { + result = 1; + goto cleanup; + } + } + + // set up attribute vector for new item (each attribute consists of {tag, length, pointer}) + SecKeychainAttribute attrs[] = { + { kSecLabelItemAttr, label ? (UInt32) strlen(label) : 0, (char *)label }, + { kSecDescriptionItemAttr, kind ? (UInt32) strlen(kind) : 0, (char *)kind }, + { kSecGenericItemAttr, value ? (UInt32) strlen(value) : 0, (char *)value }, + { kSecCommentItemAttr, comment ? (UInt32) strlen(comment) : 0, (char *)comment }, + { kSecServiceItemAttr, serviceName ? (UInt32) strlen(serviceName) : 0, (char *)serviceName }, + { kSecAccountItemAttr, accountName ? (UInt32) strlen(accountName) : 0, (char *)accountName }, + { (SecKeychainAttrType)0, sizeof(FourCharCode), NULL }, /* placeholder */ + { (SecKeychainAttrType)0, sizeof(FourCharCode), NULL } /* placeholder */ + }; + SecKeychainAttributeList attributes = { sizeof(attrs) / sizeof(attrs[0]), attrs }; + attributes.count -= 2; + + if (itemCreator != 0) + { + attrs[attributes.count].tag = kSecCreatorItemAttr; + attrs[attributes.count].data = (FourCharCode *)&itemCreator; + attributes.count++; + } + if (itemType != 0) + { + attrs[attributes.count].tag = kSecTypeItemAttr; + attrs[attributes.count].data = (FourCharCode *)&itemType; + attributes.count++; + } + + result = SecKeychainItemCreateFromContent(kSecGenericPasswordItemClass, + &attributes, + passwordLength, + passwordData, + keychain, + access, + &itemRef); + + if (result) + { + sec_error("SecKeychainItemCreateFromContent (%s): %s", keychainName ? keychainName : "", sec_errstr(result)); + } + +cleanup: + if (itemRef) + CFRelease(itemRef); + if (keychain) + CFRelease(keychain); + + return result; +} + +static int +do_update_internet_password(const char *keychainName, + FourCharCode itemCreator, + FourCharCode itemType, + const char *kind, + const char *comment, + const char *label, + const char *serverName, + const char *securityDomain, + const char *accountName, + const char *path, + UInt16 port, + SecProtocolType protocol, + SecAuthenticationType authenticationType, + const void *passwordData, + uint32_t passwordLength, + SecAccessRef access) +{ + OSStatus status; + SecKeychainRef keychainRef = NULL; + SecKeychainItemRef itemRef = NULL; + + if (keychainName) { + keychainRef = keychain_open(keychainName); + } + itemRef = find_first_internet_password(keychainRef,itemCreator,itemType,kind,comment,label,serverName, + securityDomain,accountName,path,port,protocol,authenticationType); + if (keychainRef) { + CFRelease(keychainRef); + } + if (!itemRef) { + return errSecItemNotFound; + } + + // build list of attributes + SecKeychainAttribute attrs[12]; // maximum number of attributes + SecKeychainAttributeList attrList = { 0, attrs }; + + if ((UInt32)itemCreator != 0) { + attrs[attrList.count].tag = kSecCreatorItemAttr; + attrs[attrList.count].length = sizeof(FourCharCode); + attrs[attrList.count].data = (FourCharCode *)&itemCreator; + attrList.count++; + } + if ((UInt32)itemType != 0) { + attrs[attrList.count].tag = kSecTypeItemAttr; + attrs[attrList.count].length = sizeof(FourCharCode); + attrs[attrList.count].data = (FourCharCode *)&itemType; + attrList.count++; + } + if (kind != NULL) { + attrs[attrList.count].tag = kSecDescriptionItemAttr; + attrs[attrList.count].length = (UInt32) strlen(kind); + attrs[attrList.count].data = (void *)kind; + attrList.count++; + } + if (comment != NULL) { + attrs[attrList.count].tag = kSecCommentItemAttr; + attrs[attrList.count].length = (UInt32) strlen(comment); + attrs[attrList.count].data = (void *)comment; + attrList.count++; + } + if (label != NULL) { + attrs[attrList.count].tag = kSecLabelItemAttr; + attrs[attrList.count].length = (UInt32) strlen(label); + attrs[attrList.count].data = (void *)label; + attrList.count++; + } + if (serverName != NULL) { + attrs[attrList.count].tag = kSecServerItemAttr; + attrs[attrList.count].length = (UInt32) strlen(serverName); + attrs[attrList.count].data = (void *)serverName; + attrList.count++; + } + if (securityDomain != NULL) { + attrs[attrList.count].tag = kSecSecurityDomainItemAttr; + attrs[attrList.count].length = (UInt32) strlen(securityDomain); + attrs[attrList.count].data = (void *)securityDomain; + attrList.count++; + } + if (accountName != NULL) { + attrs[attrList.count].tag = kSecAccountItemAttr; + attrs[attrList.count].length = (UInt32) strlen(accountName); + attrs[attrList.count].data = (void *)accountName; + attrList.count++; + } + if (path != NULL) { + attrs[attrList.count].tag = kSecPathItemAttr; + attrs[attrList.count].length = (UInt32) strlen(path); + attrs[attrList.count].data = (void *)path; + attrList.count++; + } + if (port != 0) { + attrs[attrList.count].tag = kSecPortItemAttr; + attrs[attrList.count].length = sizeof(UInt16); + attrs[attrList.count].data = (UInt16 *)&port; + attrList.count++; + } + if ((UInt32)protocol != 0) { + attrs[attrList.count].tag = kSecProtocolItemAttr; + attrs[attrList.count].length = sizeof(SecProtocolType); + attrs[attrList.count].data = (SecProtocolType *)&protocol; + attrList.count++; + } + if ((UInt32)authenticationType != 0) { + attrs[attrList.count].tag = kSecAuthenticationTypeItemAttr; + attrs[attrList.count].length = sizeof(SecAuthenticationType); + attrs[attrList.count].data = (SecAuthenticationType *)&authenticationType; + attrList.count++; + } + + // modify attributes and password data, if provided + status = SecKeychainItemModifyContent(itemRef, &attrList, passwordLength, passwordData); + if (status) { + sec_error("SecKeychainItemModifyContent: %s", sec_errstr(status)); + } + + // modify access, if provided + if (!status && access) { + status = modify_access(itemRef, access); + } + + CFRelease(itemRef); + + return status; +} + +static int +do_add_internet_password(const char *keychainName, + FourCharCode itemCreator, + FourCharCode itemType, + const char *kind, + const char *comment, + const char *label, + const char *serverName, + const char *securityDomain, + const char *accountName, + const char *path, + UInt16 port, + SecProtocolType protocol, + SecAuthenticationType authenticationType, + const void *passwordData, + uint32_t passwordLength, + SecAccessRef access, + Boolean update) +{ + SecKeychainRef keychain = NULL; + SecKeychainItemRef itemRef = NULL; + OSStatus result = 0; + + // if update flag is specified, try to find and update an existing item + if (update) { + result = do_update_internet_password(keychainName,itemCreator,itemType,kind,comment,label,serverName, + securityDomain,accountName,path,port,protocol,authenticationType, + passwordData,passwordLength,access); + if (result == noErr) + return result; + } + + if (keychainName) + { + keychain = keychain_open(keychainName); + if (!keychain) + { + result = 1; + goto cleanup; + } + } + + // set up attribute vector for new item (each attribute consists of {tag, length, pointer}) + SecKeychainAttribute attrs[] = { + { kSecLabelItemAttr, label ? (UInt32) strlen(label) : 0, (char *)label }, + { kSecDescriptionItemAttr, kind ? (UInt32) strlen(kind) : 0, (char *)kind }, + { kSecCommentItemAttr, comment ? (UInt32) strlen(comment) : 0, (char *)comment }, + { kSecServerItemAttr, serverName ? (UInt32) strlen(serverName) : 0, (char *)serverName }, + { kSecSecurityDomainItemAttr, securityDomain ? (UInt32) strlen(securityDomain) : 0, (char *)securityDomain }, + { kSecAccountItemAttr, accountName ? (UInt32) strlen(accountName) : 0, (char *)accountName }, + { kSecPathItemAttr, path ? (UInt32) strlen(path) : 0, (char *)path }, + { kSecPortItemAttr, sizeof(UInt16), (UInt16 *)&port }, + { kSecProtocolItemAttr, sizeof(SecProtocolType), (SecProtocolType *)&protocol }, + { kSecAuthenticationTypeItemAttr, sizeof(SecAuthenticationType), (SecAuthenticationType *)&authenticationType }, + { (SecKeychainAttrType)0, sizeof(FourCharCode), NULL }, /* placeholder */ + { (SecKeychainAttrType)0, sizeof(FourCharCode), NULL } /* placeholder */ + }; + SecKeychainAttributeList attributes = { sizeof(attrs) / sizeof(attrs[0]), attrs }; + attributes.count -= 2; + + if (itemCreator != 0) + { + attrs[attributes.count].tag = kSecCreatorItemAttr; + attrs[attributes.count].data = (FourCharCode *)&itemCreator; + attributes.count++; + } + if (itemType != 0) + { + attrs[attributes.count].tag = kSecTypeItemAttr; + attrs[attributes.count].data = (FourCharCode *)&itemType; + attributes.count++; + } + + result = SecKeychainItemCreateFromContent(kSecInternetPasswordItemClass, + &attributes, + passwordLength, + passwordData, + keychain, + access, + &itemRef); + + if (result) + { + sec_error("SecKeychainAddInternetPassword %s: %s", keychainName ? keychainName : "", sec_errstr(result)); + } + +cleanup: + if (itemRef) + CFRelease(itemRef); + if (keychain) + CFRelease(keychain); + + return result; +} + +static int +do_add_certificates(const char *keychainName, int argc, char * const *argv) +{ + SecKeychainRef keychain = NULL; + int ix, result = 0; + + if (keychainName) + { + keychain = keychain_open(keychainName); + if (!keychain) + { + result = 1; + goto cleanup; + } + } + + for (ix = 0; ix < argc; ++ix) + { + CSSM_DATA certData = {}; + OSStatus status; + SecCertificateRef certificate = NULL; + + if (read_file(argv[ix], &certData)) + { + result = 1; + continue; + } + + status = SecCertificateCreateFromData(&certData, CSSM_CERT_X_509v3, CSSM_CERT_ENCODING_UNKNOWN, &certificate); + if (status) + { + sec_perror("SecCertificateCreateFromData", status); + result = 1; + } + else + { + status = SecCertificateAddToKeychain(certificate, keychain); + if (status) + { + if (status == errSecDuplicateItem) + { + if (keychainName) + sec_error("%s: already in %s", argv[ix], keychainName); + else + sec_error("%s: already in default keychain", argv[ix]); + } + else + { + sec_perror("SecCertificateAddToKeychain", status); + } + result = 1; + } + } + + if (certData.Data) + free(certData.Data); + if (certificate) + CFRelease(certificate); + } + +cleanup: + if (keychain) + CFRelease(keychain); + + return result; +} + +static bool convertPasswordData(const char *hexString, char **outData, uint32_t *outLength) { + size_t length = 0; + bool result = convertHex(hexString, (uint8_t **)outData, &length); + if (result && outLength) { + *outLength = (uint32_t)length & 0xFFFFFFFF; + } + return result; +} + +static bool promptForPasswordData(char **returnedPasswordData) { + // Caller must zero memory and free returned value. + // Returns true if we have data; false means the user canceled + if (!returnedPasswordData) + return false; + + bool result = false; + char *password = NULL; + int tries; + + for (tries = 3; tries-- > 0;) { + bool passwordsMatch = false; + char *firstpass = NULL; + + password = getpass("password data for new item: "); + if (!password) + break; + + firstpass = malloc(strlen(password) + 1); + strcpy(firstpass, password); + password = getpass("retype password for new item: "); + passwordsMatch = password && (strcmp(password, firstpass) == 0); + memset(firstpass, 0, strlen(firstpass)); + free(firstpass); + if (!password) + break; + + if (passwordsMatch) { + result = true; + break; + } + + fprintf(stderr, "passwords don't match\n"); + memset(password, 0, strlen(password)); + } + + if (result) { + *returnedPasswordData = password; + } else { + free(password); + } + return result; +} + +int +keychain_add_generic_password(int argc, char * const *argv) +{ + char *serviceName = NULL, *passwordData = NULL, *accountName = NULL; + char *kind = NULL, *label = NULL, *value = NULL, *comment = NULL; + FourCharCode itemCreator = 0, itemType = 0; + int ch, result = 0; + uint32_t passwordLength = 0; + const char *keychainName = NULL; + Boolean access_specified = FALSE; + Boolean always_allow = FALSE; + Boolean update_item = FALSE; + SecAccessRef access = NULL; + CFMutableArrayRef trusted_list = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); + OSStatus status; + bool mustFreePasswordData = false; // if we got it via user prompting + + /* + * " -a Specify account name (required)\n" + * " -c Specify item creator (optional four-character code)\n" + * " -C Specify item type (optional four-character code)\n" + * " -D Specify kind (default is \"application password\")\n" + * " -G Specify generic attribute (optional)\n" + * " -j Specify comment string (optional)\n" + * " -l Specify label (if omitted, service name is used as default label)\n" + * " -s Specify service name (required)\n" + * " -p Specify password to be added (legacy option, equivalent to -w)\n" + * " -w Specify password to be added\n" + * " -X Specify password data to be added as a hexadecimal string\n" + * " -A Allow any application to access this item without warning (insecure, not recommended!)\n" + * " -T Specify an application which may access this item (multiple -T options are allowed)\n" + * " -U Update item if it already exists (if omitted, the item cannot already exist)\n" + */ + + while ((ch = getopt(argc, argv, ":a:c:C:D:G:j:l:s:p:w:X:UAT:h")) != -1) + { + switch (ch) + { + case 'a': + accountName = optarg; + break; + case 'c': + result = parse_fourcharcode(optarg, &itemCreator); + if (result) goto cleanup; + break; + case 'C': + result = parse_fourcharcode(optarg, &itemType); + if (result) goto cleanup; + break; + case 'D': + kind = optarg; + break; + case 'G': + value = optarg; + break; + case 'j': + comment = optarg; + break; + case 'l': + label = optarg; + break; + case 's': + serviceName = optarg; + break; + case 'p': + case 'w': + passwordData = optarg; + passwordLength = (passwordData) ? (uint32_t)strlen(passwordData) : 0; + break; + case 'U': + update_item = TRUE; + break; + case 'A': + always_allow = TRUE; + access_specified = TRUE; + break; + case 'T': + { + if (optarg[0]) + { + SecTrustedApplicationRef app = NULL; + /* check whether the argument specifies an application group */ + const char *groupPrefix = "group://"; + size_t prefixLen = strlen(groupPrefix); + if (strlen(optarg) > prefixLen && !memcmp(optarg, groupPrefix, prefixLen)) { + const char *groupName = &optarg[prefixLen]; + if ((status = SecTrustedApplicationCreateApplicationGroup(groupName, NULL, &app)) != noErr) { + sec_error("SecTrustedApplicationCreateApplicationGroup %s: %s", optarg, sec_errstr(status)); + } + } else { + if ((status = SecTrustedApplicationCreateFromPath(optarg, &app)) != noErr) { + sec_error("SecTrustedApplicationCreateFromPath %s: %s", optarg, sec_errstr(status)); + } + } + + if (status) { + result = 1; + goto cleanup; + } + + CFArrayAppendValue(trusted_list, app); + CFRelease(app); + } + access_specified = TRUE; + break; + } + case 'X': + if (convertPasswordData(optarg, &passwordData, &passwordLength)) { + mustFreePasswordData = true; + break; + } + sec_error("Unable to convert password data (-X must specify valid hex digits)"); + result = 2; + goto cleanup; /* @@@ Return 2 triggers usage message. */ + case '?': + case ':': + // They supplied the -p or -w but with no data, so prompt + // This differs from the case where no -p or -w was given, where we set the data to empty + if (optopt == 'p' || optopt == 'w') { + if (promptForPasswordData(&passwordData)) { + passwordLength = (passwordData) ? (uint32_t)strlen(passwordData) : 0; + mustFreePasswordData = true; + break; + } else { + result = 1; + goto cleanup; /* @@@ Do not trigger usage message, but indicate failure. */ + } + } + result = 2; + goto cleanup; /* @@@ Return 2 triggers usage message. */ + default: + result = 2; + goto cleanup; /* @@@ Return 2 triggers usage message. */ + } + } + + argc -= optind; + argv += optind; + + if (!accountName || !serviceName) + { + result = 2; + goto cleanup; + } + else if (argc > 0) + { + keychainName = argv[0]; + if (argc > 1 || *keychainName == '\0') + { + result = 2; + goto cleanup; + } + } + + if (access_specified) + { + const char *accessName = (label) ? label : (serviceName) ? serviceName : (accountName) ? accountName : ""; + if ((result = create_access(accessName, always_allow, trusted_list, &access)) != 0) + goto cleanup; + } + + result = do_add_generic_password(keychainName, + itemCreator, + itemType, + kind, + value, + comment, + (label) ? label : serviceName, + serviceName, + accountName, + passwordData, + passwordLength, + access, + update_item); + +cleanup: + if (mustFreePasswordData) + free(passwordData); + if (trusted_list) + CFRelease(trusted_list); + if (access) + CFRelease(access); + + return result; +} + +int +keychain_add_internet_password(int argc, char * const *argv) +{ + char *serverName = NULL, *securityDomain = NULL, *accountName = NULL, *path = NULL, *passwordData = NULL; + char *kind = NULL, *comment = NULL, *label = NULL; + FourCharCode itemCreator = 0, itemType = 0; + UInt16 port = 0; + SecProtocolType protocol = 0; + SecAuthenticationType authenticationType = OSSwapHostToBigInt32('dflt'); + int ch, result = 0; + uint32_t passwordLength = 0; + const char *keychainName = NULL; + Boolean access_specified = FALSE; + Boolean always_allow = FALSE; + Boolean update_item = FALSE; + SecAccessRef access = NULL; + CFMutableArrayRef trusted_list = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); + OSStatus status; + bool mustFreePasswordData = false; // if we got it via user prompting + + /* + * " -a Specify account name (required)\n" + * " -c Specify item creator (optional four-character code)\n" + * " -C Specify item type (optional four-character code)\n" + * " -d Specify security domain string (optional)\n" + * " -D Specify kind (default is \"Internet password\")\n" + * " -j Specify comment string (optional)\n" + * " -l Specify label (if omitted, server name is used as default label)\n" + * " -p Specify path string (optional)\n" + * " -P Specify port number (optional)\n" + * " -r Specify protocol (optional four-character SecProtocolType, e.g. \"http\", \"ftp \")\n" + * " -s Specify server name (required)\n" + * " -t Specify authentication type (as a four-character SecAuthenticationType, default is \"dflt\")\n" + * " -w Specify password to be added\n" + * " -X Specify password data to be added as a hexadecimal string\n" + * " -A Allow any application to access this item without warning (insecure, not recommended!)\n" + * " -T Specify an application which may access this item (multiple -T options are allowed)\n" + * " -U Update item if it already exists (if omitted, the item cannot already exist)\n" + */ + + while ((ch = getopt(argc, argv, ":a:c:C:d:D:j:l:p:P:r:s:t:w:X:UAT:h")) != -1) + { + switch (ch) + { + case 'a': + accountName = optarg; + break; + case 'c': + result = parse_fourcharcode(optarg, &itemCreator); + if (result) goto cleanup; + break; + case 'C': + result = parse_fourcharcode(optarg, &itemType); + if (result) goto cleanup; + break; + case 'd': + securityDomain = optarg; + break; + case 'D': + kind = optarg; + break; + case 'j': + comment = optarg; + break; + case 'l': + label = optarg; + break; + case 'p': + path = optarg; + break; + case 'P': + port = atoi(optarg); + break; + case 'r': + result = parse_fourcharcode(optarg, &protocol); + if (result) goto cleanup; + break; + case 's': + serverName = optarg; + break; + case 't': + result = parse_fourcharcode(optarg, &authenticationType); + if (result) goto cleanup; + /* auth type attribute is special */ + authenticationType = OSSwapHostToBigInt32(authenticationType); + break; + case 'w': + passwordData = optarg; + passwordLength = (passwordData) ? (uint32_t)strlen(passwordData) : 0; + break; + case 'U': + update_item = TRUE; + break; + case 'A': + always_allow = TRUE; + access_specified = TRUE; + break; + case 'T': + { + if (optarg[0]) + { + SecTrustedApplicationRef app = NULL; + /* check whether the argument specifies an application group */ + const char *groupPrefix = "group://"; + size_t prefixLen = strlen(groupPrefix); + if (strlen(optarg) > prefixLen && !memcmp(optarg, groupPrefix, prefixLen)) { + const char *groupName = &optarg[prefixLen]; + if ((status = SecTrustedApplicationCreateApplicationGroup(groupName, NULL, &app)) != noErr) { + sec_error("SecTrustedApplicationCreateApplicationGroup %s: %s", optarg, sec_errstr(status)); + } + } else { + if ((status = SecTrustedApplicationCreateFromPath(optarg, &app)) != noErr) { + sec_error("SecTrustedApplicationCreateFromPath %s: %s", optarg, sec_errstr(status)); + } + } + + if (status) { + result = 1; + goto cleanup; + } + + CFArrayAppendValue(trusted_list, app); + CFRelease(app); + } + access_specified = TRUE; + break; + } + case 'X': + if (convertPasswordData(optarg, &passwordData, &passwordLength)) { + mustFreePasswordData = true; + break; + } + sec_error("Unable to convert password data (-X must specify valid hex digits)"); + result = 2; + goto cleanup; /* @@@ Return 2 triggers usage message. */ + case '?': + case ':': + // They supplied the -p or -w but with no data, so prompt + // This differs from the case where no -p or -w was given, where we set the data to empty + if (optopt == 'p' || optopt == 'w') { + if (promptForPasswordData(&passwordData)) { + passwordLength = (passwordData) ? (uint32_t)strlen(passwordData) : 0; + mustFreePasswordData = true; + break; + } else { + result = 1; + goto cleanup; /* @@@ Do not trigger usage message, but indicate failure. */ + } + } + result = 2; + goto cleanup; /* @@@ Return 2 triggers usage message. */ + + default: + result = 2; + goto cleanup; /* @@@ Return 2 triggers usage message. */ + } + } + + argc -= optind; + argv += optind; + + if (!accountName || !serverName) + { + result = 2; + goto cleanup; + } + else if (argc > 0) + { + keychainName = argv[0]; + if (argc > 1 || *keychainName == '\0') + { + result = 2; + goto cleanup; + } + } + + if (access_specified) + { + const char *accessName = (label) ? label : (serverName) ? serverName : (accountName) ? accountName : ""; + if ((result = create_access(accessName, always_allow, trusted_list, &access)) != 0) + goto cleanup; + } + + result = do_add_internet_password(keychainName, + itemCreator, + itemType, + kind, + comment, + (label) ? label : serverName, + serverName, + securityDomain, + accountName, + path, + port, + protocol, + authenticationType, + passwordData, + passwordLength, + access, + update_item); + +cleanup: + if (mustFreePasswordData) + free(passwordData); + if (trusted_list) + CFRelease(trusted_list); + if (access) + CFRelease(access); + + return result; +} + +int +keychain_add_certificates(int argc, char * const *argv) +{ + int ch, result = 0; + const char *keychainName = NULL; + while ((ch = getopt(argc, argv, "hk:")) != -1) + { + switch (ch) + { + case 'k': + keychainName = optarg; + if (*keychainName == '\0') + return SHOW_USAGE_MESSAGE; + break; + case '?': + default: + return SHOW_USAGE_MESSAGE; + } + } + + argc -= optind; + argv += optind; + + if (argc == 0) + return SHOW_USAGE_MESSAGE; + + result = do_add_certificates(keychainName, argc, argv); + + return result; +}