/* * Copyright (c) 2003-2009 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.h" #include "security.h" #include "access_utils.h" #include "keychain_utilities.h" #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, 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 = strlen(kind); attrs[attrList.count].data = (void *)kind; attrList.count++; } if (value != NULL) { attrs[attrList.count].tag = kSecGenericItemAttr; attrs[attrList.count].length = strlen(value); attrs[attrList.count].data = (void *)value; attrList.count++; } if (comment != NULL) { attrs[attrList.count].tag = kSecCommentItemAttr; attrs[attrList.count].length = strlen(comment); attrs[attrList.count].data = (void *)comment; attrList.count++; } if (label != NULL) { attrs[attrList.count].tag = kSecLabelItemAttr; attrs[attrList.count].length = strlen(label); attrs[attrList.count].data = (void *)label; attrList.count++; } if (serviceName != NULL) { attrs[attrList.count].tag = kSecServiceItemAttr; attrs[attrList.count].length = strlen(serviceName); attrs[attrList.count].data = (void *)serviceName; attrList.count++; } if (accountName != NULL) { attrs[attrList.count].tag = kSecAccountItemAttr; attrs[attrList.count].length = strlen(accountName); attrs[attrList.count].data = (void *)accountName; attrList.count++; } // modify attributes and password data, if provided status = SecKeychainItemModifyContent(itemRef, &attrList, (passwordData) ? strlen(passwordData) : 0, 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, 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,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 ? strlen(label) : 0, (char *)label }, { kSecDescriptionItemAttr, kind ? strlen(kind) : 0, (char *)kind }, { kSecGenericItemAttr, value ? strlen(value) : 0, (char *)value }, { kSecCommentItemAttr, comment ? strlen(comment) : 0, (char *)comment }, { kSecServiceItemAttr, serviceName ? strlen(serviceName) : 0, (char *)serviceName }, { kSecAccountItemAttr, accountName ? 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, passwordData ? strlen(passwordData) : 0, 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, 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 = strlen(kind); attrs[attrList.count].data = (void *)kind; attrList.count++; } if (comment != NULL) { attrs[attrList.count].tag = kSecCommentItemAttr; attrs[attrList.count].length = strlen(comment); attrs[attrList.count].data = (void *)comment; attrList.count++; } if (label != NULL) { attrs[attrList.count].tag = kSecLabelItemAttr; attrs[attrList.count].length = strlen(label); attrs[attrList.count].data = (void *)label; attrList.count++; } if (serverName != NULL) { attrs[attrList.count].tag = kSecServerItemAttr; attrs[attrList.count].length = strlen(serverName); attrs[attrList.count].data = (void *)serverName; attrList.count++; } if (securityDomain != NULL) { attrs[attrList.count].tag = kSecSecurityDomainItemAttr; attrs[attrList.count].length = strlen(securityDomain); attrs[attrList.count].data = (void *)securityDomain; attrList.count++; } if (accountName != NULL) { attrs[attrList.count].tag = kSecAccountItemAttr; attrs[attrList.count].length = strlen(accountName); attrs[attrList.count].data = (void *)accountName; attrList.count++; } if (path != NULL) { attrs[attrList.count].tag = kSecPathItemAttr; attrs[attrList.count].length = 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, (passwordData) ? strlen(passwordData) : 0, 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, 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,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 ? strlen(label) : 0, (char *)label }, { kSecDescriptionItemAttr, kind ? strlen(kind) : 0, (char *)kind }, { kSecCommentItemAttr, comment ? strlen(comment) : 0, (char *)comment }, { kSecServerItemAttr, serverName ? strlen(serverName) : 0, (char *)serverName }, { kSecSecurityDomainItemAttr, securityDomain ? strlen(securityDomain) : 0, (char *)securityDomain }, { kSecAccountItemAttr, accountName ? strlen(accountName) : 0, (char *)accountName }, { kSecPathItemAttr, path ? 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, passwordData ? strlen(passwordData) : 0, 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; } 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; 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; /* * " -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" * " -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:UAT:")) != -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; 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 '?': 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, access, update_item); cleanup: 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; 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; /* * " -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" * " -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: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; break; case 'w': passwordData = optarg; 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 '?': 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, access, update_item); cleanup: 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 2; break; case '?': default: return 2; /* @@@ Return 2 triggers usage message. */ } } argc -= optind; argv += optind; if (argc == 0) return 2; result = do_add_certificates(keychainName, argc, argv); return result; }