1// 2// keychain_test.m 3// Keychain item access control example 4// 5// Created by Perry Kiehtreiber on Wed Jun 19 2002 6// Modified by Ken McLeod, Mon Apr 21 2003 -- added "always allow" ACL support 7// Wed Jul 28 2004 -- add test code for persistent ref SPI 8// Mon Aug 02 2004 -- add test code to change label attributes 9// 10// To build and run this example: 11// cc -framework Security -framework Foundation keychain_test.m ; ./a.out 12// 13// Copyright (c) 2003-2005 Apple Computer, Inc. All Rights Reserved. 14// 15 16#define TEST_PERSISTENT_REFS 0 17#define USE_SYSTEM_KEYCHAIN 0 18 19 20#import <Cocoa/Cocoa.h> 21 22#include <Security/SecBase.h> 23#include <Security/SecKeychain.h> 24#include <Security/SecKeychainItem.h> 25#include <Security/SecKeychainItemPriv.h> 26#include <Security/SecKeychainSearch.h> 27#include <Security/SecAccess.h> 28#include <Security/SecTrustedApplication.h> 29#include <Security/SecACL.h> 30 31#import "testmore.h" 32#import "testenv.h" 33#import "testleaks.h" 34 35SecAccessRef createAccess(NSString *accessLabel, BOOL allowAny) 36{ 37 SecAccessRef access=nil; 38 NSArray *trustedApplications=nil; 39 40 if (!allowAny) // use default access ("confirm access") 41 { 42 // make an exception list of applications you want to trust, which 43 // are allowed to access the item without requiring user confirmation 44 SecTrustedApplicationRef myself, someOther; 45 ok_status(SecTrustedApplicationCreateFromPath(NULL, &myself), 46 "create trusted app for self"); 47 ok_status(SecTrustedApplicationCreateFromPath("/Applications/Mail.app", 48 &someOther), "create trusted app for Mail.app"); 49 trustedApplications = [NSArray arrayWithObjects:(id)myself, 50 (id)someOther, nil]; 51 CFRelease(myself); 52 CFRelease(someOther); 53 } 54 55 ok_status(SecAccessCreate((CFStringRef)accessLabel, 56 (CFArrayRef)trustedApplications, &access), "SecAccessCreate"); 57 58 if (allowAny) 59 { 60 // change access to be wide-open for decryption ("always allow access") 61 // get the access control list for decryption operations 62 CFArrayRef aclList=nil; 63 ok_status(SecAccessCopySelectedACLList(access, 64 CSSM_ACL_AUTHORIZATION_DECRYPT, &aclList), 65 "SecAccessCopySelectedACLList"); 66 67 // get the first entry in the access control list 68 SecACLRef aclRef=(SecACLRef)CFArrayGetValueAtIndex(aclList, 0); 69 CFArrayRef appList=nil; 70 CFStringRef promptDescription=nil; 71 CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR promptSelector; 72 ok_status(SecACLCopySimpleContents(aclRef, &appList, 73 &promptDescription, &promptSelector), "SecACLCopySimpleContents"); 74 75 // modify the default ACL to not require the passphrase, and have a 76 // nil application list 77 promptSelector.flags &= ~CSSM_ACL_KEYCHAIN_PROMPT_REQUIRE_PASSPHRASE; 78 ok_status(SecACLSetSimpleContents(aclRef, NULL, promptDescription, 79 &promptSelector), "SecACLSetSimpleContents"); 80 81 if (appList) CFRelease(appList); 82 if (promptDescription) CFRelease(promptDescription); 83 if (aclList) CFRelease(aclList); 84 } 85 86 return access; 87} 88 89 90void addApplicationPassword(SecKeychainRef keychain, NSString *password, 91 NSString *account, NSString *service, BOOL allowAny) 92{ 93 SecKeychainItemRef item = nil; 94 const char *serviceUTF8 = [service UTF8String]; 95 const char *accountUTF8 = [account UTF8String]; 96 const char *passwordUTF8 = [password UTF8String]; 97 // use the service string as the name of this item for display purposes 98 NSString *itemLabel = service; 99 const char *itemLabelUTF8 = [itemLabel UTF8String]; 100 101#if USE_SYSTEM_KEYCHAIN 102 const char *sysKeychainPath = "/Library/Keychains/System.keychain"; 103 status = SecKeychainOpen(sysKeychainPath, &keychain); 104 if (status) { NSLog(@"SecKeychainOpen returned %d", status); return; } 105 status = SecKeychainSetPreferenceDomain(kSecPreferencesDomainSystem); 106 if (status) { NSLog(@"SecKeychainSetPreferenceDomain returned %d", status); return; } 107#endif 108 109 // create initial access control settings for the item 110 SecAccessRef access = createAccess(itemLabel, allowAny); 111 112 // Below is the lower-layer equivalent to the 113 // SecKeychainAddGenericPassword() function; it does the same thing 114 // (except specify the access controls) set up attribute vector 115 // (each attribute consists of {tag, length, pointer}) 116 SecKeychainAttribute attrs[] = { 117 { kSecLabelItemAttr, strlen(itemLabelUTF8), (char *)itemLabelUTF8 }, 118 { kSecAccountItemAttr, strlen(accountUTF8), (char *)accountUTF8 }, 119 { kSecServiceItemAttr, strlen(serviceUTF8), (char *)serviceUTF8 } 120 }; 121 SecKeychainAttributeList attributes = 122 { sizeof(attrs) / sizeof(attrs[0]), attrs }; 123 124 ok_status(SecKeychainItemCreateFromContent(kSecGenericPasswordItemClass, 125 &attributes, strlen(passwordUTF8), passwordUTF8, keychain, access, 126 &item), "SecKeychainItemCreateFromContent"); 127 128 if (access) CFRelease(access); 129 if (item) CFRelease(item); 130} 131 132 133void addInternetPassword(SecKeychainRef keychain, NSString *password, 134 NSString *account, NSString *server, NSString *path, 135 SecProtocolType protocol, int port, BOOL allowAny) 136{ 137 SecKeychainItemRef item = nil; 138 const char *pathUTF8 = [path UTF8String]; 139 const char *serverUTF8 = [server UTF8String]; 140 const char *accountUTF8 = [account UTF8String]; 141 const char *passwordUTF8 = [password UTF8String]; 142 // use the server string as the name of this item for display purposes 143 NSString *itemLabel = server; 144 const char *itemLabelUTF8 = [itemLabel UTF8String]; 145 146#if USE_SYSTEM_KEYCHAIN 147 const char *sysKeychainPath = "/Library/Keychains/System.keychain"; 148 status = SecKeychainOpen(sysKeychainPath, &keychain); 149 if (status) { NSLog(@"SecKeychainOpen returned %d", status); return 1; } 150 status = SecKeychainSetPreferenceDomain(kSecPreferencesDomainSystem); 151 if (status) { NSLog(@"SecKeychainSetPreferenceDomain returned %d", status); return 1; } 152#endif 153 154 // create initial access control settings for the item 155 SecAccessRef access = createAccess(itemLabel, allowAny); 156 157 // below is the lower-layer equivalent to the 158 // SecKeychainAddInternetPassword() function; it does the same 159 // thing (except specify the access controls) set up attribute 160 // vector (each attribute consists of {tag, length, pointer}) 161 SecKeychainAttribute attrs[] = { 162 { kSecLabelItemAttr, strlen(itemLabelUTF8), (char *)itemLabelUTF8 }, 163 { kSecAccountItemAttr, strlen(accountUTF8), (char *)accountUTF8 }, 164 { kSecServerItemAttr, strlen(serverUTF8), (char *)serverUTF8 }, 165 { kSecPortItemAttr, sizeof(int), (int *)&port }, 166 { kSecProtocolItemAttr, sizeof(SecProtocolType), 167 (SecProtocolType *)&protocol }, 168 { kSecPathItemAttr, strlen(pathUTF8), (char *)pathUTF8 } 169 }; 170 SecKeychainAttributeList attributes = 171 { sizeof(attrs) / sizeof(attrs[0]), attrs }; 172 173 ok_status(SecKeychainItemCreateFromContent(kSecInternetPasswordItemClass, 174 &attributes, strlen(passwordUTF8), passwordUTF8, keychain, access, 175 &item), "SecKeychainItemCreateFromContent"); 176 177//*** code to test persistent reference SPI 178#if TEST_PERSISTENT_REFS 179 CFDataRef persistentRef = NULL; 180 SecKeychainItemRef item2 = NULL; 181 status = SecKeychainItemCopyPersistentReference(item, &persistentRef); 182 if (!status) 183 { 184 NSLog(@"Created persistent reference for item %@:\n%@", item, 185 persistentRef); 186 status = SecKeychainItemFromPersistentReference(persistentRef, &item2); 187 if (!status) 188 NSLog(@"SUCCESS: Got item from persistent reference (%@)", item2); 189 else 190 NSLog(@"ERROR: unable to reconsitute item (%d)", status); 191 } 192 else 193 { 194 NSLog(@"ERROR: unable to create persistent reference (%d)", status); 195 } 196 //[(NSData*)pref writeToFile:@"/tmp/persistentData" atomically:YES]; 197 if (item2) CFRelease(item2); 198 if (persistentRef) CFRelease(persistentRef); 199#endif 200//*** end persistent reference test code 201 202 if (access) CFRelease(access); 203 if (item) CFRelease(item); 204} 205 206void testLabelChange(SecKeychainRef keychain) 207{ 208 // Find each generic password item in any keychain whose label 209 // is "sample service", and modify the existing label attribute 210 // by adding a " [label]" suffix. (Note that if the Keychain 211 // Access app is running, you may need to quit and relaunch it to 212 // see the changes, due to notification bugs.) 213 214 const char *searchString = "sample service"; 215 const char *labelSuffix = " [label]"; 216 217#if USE_SYSTEM_KEYCHAIN 218 const char *sysKeychainPath = "/Library/Keychains/System.keychain"; 219 status = SecKeychainOpen(sysKeychainPath, &keychain); 220 if (status) { NSLog(@"SecKeychainOpen returned %d", status); return 0; } 221 status = SecKeychainSetPreferenceDomain(kSecPreferencesDomainSystem); 222 if (status) { NSLog(@"SecKeychainSetPreferenceDomain returned %d", status); return 0; } 223#endif 224 225 SecKeychainSearchRef searchRef = nil; 226 SecKeychainAttribute sAttrs[] = 227 { { kSecServiceItemAttr, strlen(searchString), (char*)searchString } }; 228 SecKeychainAttributeList sAttrList = 229 { sizeof(sAttrs) / sizeof(sAttrs[0]), sAttrs }; 230 ok_status(SecKeychainSearchCreateFromAttributes(keychain, 231 kSecGenericPasswordItemClass, &sAttrList, &searchRef), 232 "SecKeychainSearchCreateFromAttributes"); 233 234 SecKeychainItemRef foundItemRef = NULL; 235 int count; 236 for (count = 0; count < 2; ++count) 237 { 238 ok_status(SecKeychainSearchCopyNext(searchRef, &foundItemRef), 239 "SecKeychainSearchCopyNext"); 240 241 // get the item's label attribute (allocated for us by 242 // SecKeychainItemCopyContent, must free later...) 243 SecKeychainAttribute itemAttrs[] = { { kSecLabelItemAttr, 0, NULL } }; 244 SecKeychainAttributeList itemAttrList = 245 { sizeof(itemAttrs) / sizeof(itemAttrs[0]), itemAttrs }; 246 247 ok_status(SecKeychainItemCopyContent(foundItemRef, NULL, &itemAttrList, 248 NULL, NULL), "get label"); 249 250 // malloc enough space to hold our new label string 251 // (length = old label string + suffix string + terminating NULL) 252 CFIndex newLen = itemAttrs[0].length + strlen(labelSuffix); 253 char *p = (char*) malloc(newLen); 254 memcpy(p, itemAttrs[0].data, itemAttrs[0].length); 255 memcpy(p + itemAttrs[0].length, labelSuffix, strlen(labelSuffix)); 256 257 // set up the attribute we want to change with its new value 258 SecKeychainAttribute newAttrs[] = { { kSecLabelItemAttr, newLen, p } }; 259 SecKeychainAttributeList newAttrList = 260 { sizeof(newAttrs) / sizeof(newAttrs[0]), newAttrs }; 261 262 // modify the attribute 263 ok_status(SecKeychainItemModifyContent(foundItemRef, &newAttrList, 264 0, NULL), "modify label PR-3751523"); 265 266 // free the memory we allocated for the new label string 267 free(p); 268 269 // free the memory in the itemAttrList structure which was 270 // allocated by SecKeychainItemCopyContent 271 ok_status(SecKeychainItemFreeContent(&itemAttrList, NULL), 272 "SecKeychainItemFreeContent"); 273 274 if (foundItemRef) 275 CFRelease(foundItemRef); 276 } 277 278 is_status(SecKeychainSearchCopyNext(searchRef, &foundItemRef), 279 errSecItemNotFound, "SecKeychainSearchCopyNext at end"); 280 281 if (searchRef) CFRelease(searchRef); 282} 283 284void tests(void) 285{ 286 SecKeychainRef keychain = NULL; 287 ok_status(SecKeychainCreate("login.keychain", 4, "test", NO, NULL, 288 &keychain), "SecKeychainCreate"); 289 290 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; 291 292 // add some example passwords to the keychain 293 addApplicationPassword(keychain, @"sample password", 294 @"sample account", @"sample service", NO); 295 addApplicationPassword(keychain, @"sample password", 296 @"different account", @"sample service", NO); 297 addApplicationPassword(keychain, @"sample password", 298 @"sample account", @"sample unprotected service", YES); 299 addInternetPassword(keychain, @"sample password", 300 @"sample account", @"samplehost.apple.com", 301 @"cgi-bin/bogus/testpath", kSecProtocolTypeHTTP, 8080, NO); 302 303 // test searching and changing item label attributes 304 testLabelChange(keychain); 305 306 [pool release]; 307 308 SKIP: { 309 skip("no keychain", 1, keychain); 310 ok_status(SecKeychainDelete(keychain), "SecKeychainDelete"); 311 CFRelease(keychain); 312 } 313 314 tests_end(1); 315} 316 317int main(int argc, char * const *argv) 318{ 319 plan_tests(30); 320 tests_begin(argc, argv); 321 322 tests(); 323 324 ok_leaks("leaks"); 325 326 return 0; 327} 328