]> git.saurik.com Git - apple/security.git/blobdiff - SecurityTool/keychain_add.c
Security-57031.1.35.tar.gz
[apple/security.git] / SecurityTool / keychain_add.c
diff --git a/SecurityTool/keychain_add.c b/SecurityTool/keychain_add.c
new file mode 100644 (file)
index 0000000..c09f779
--- /dev/null
@@ -0,0 +1,898 @@
+/*
+ * Copyright (c) 2003-2009,2011-2014 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <libkern/OSByteOrder.h>
+#include <Security/SecAccess.h>
+#include <Security/SecCertificate.h>
+#include <Security/SecKeychain.h>
+#include <Security/SecKeychainItem.h>
+#include <Security/SecTrustedApplication.h>
+
+// SecTrustedApplicationCreateApplicationGroup
+#include <Security/SecTrustedApplicationPriv.h>
+
+
+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 : "<default>", 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 : "<NULL>", 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;
+                       /* auth type attribute is special */
+                       authenticationType = OSSwapHostToBigInt32(authenticationType);
+                       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;
+}