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,2007 Apple 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
35void renameItemViaModifyAttributesAndData(SecKeychainItemRef item)
36{
37	const char *labelSuffix = " [MAD]";
38
39    // get the item's label attribute (allocated for us by
40    // SecKeychainItemCopyAttributesAndData, must free later...)
41    UInt32 itemTags[] = { kSecLabelItemAttr };
42    UInt32 itemFmts[] = { CSSM_DB_ATTRIBUTE_FORMAT_STRING };
43    SecKeychainAttributeInfo attrInfo = { 1, itemTags, itemFmts };
44    SecKeychainAttributeList *attrList = NULL;
45    SecItemClass itemClass;
46
47    ok_status(SecKeychainItemCopyAttributesAndData(item, &attrInfo, &itemClass, &attrList, NULL, NULL),
48        "get label attribute");
49    
50    ok(attrList && attrList->count == 1, "check that exactly one attribute was returned");
51
52    // malloc enough space to hold our new label string
53    // (length = old label string + suffix string + terminating NULL)
54    CFIndex newLen = attrList->attr[0].length + strlen(labelSuffix);
55    char *p = (char*) malloc(newLen);
56    memcpy(p, attrList->attr[0].data, attrList->attr[0].length);
57    memcpy(p + attrList->attr[0].length, labelSuffix, strlen(labelSuffix));
58
59    // set up the attribute we want to change with its new value
60    SecKeychainAttribute newAttrs[] = { { kSecLabelItemAttr, newLen, p } };
61    SecKeychainAttributeList newAttrList =
62    { sizeof(newAttrs) / sizeof(newAttrs[0]), newAttrs };
63
64    // modify the attribute
65    ok_status(SecKeychainItemModifyAttributesAndData(item, &newAttrList, 0, NULL),
66        "SecKeychainItemModifyAttributesAndData");
67
68    // free the memory we allocated for the new label string
69    free(p);
70
71    // free the attrList which was allocated by SecKeychainItemCopyAttributesAndData
72    ok_status(SecKeychainItemFreeAttributesAndData(attrList, NULL),
73        "SecKeychainItemFreeAttributesAndData");
74}
75
76void renameItemViaModifyContent(SecKeychainItemRef item)
77{
78	const char *labelSuffix = " [MC]";
79
80    // get the item's label attribute (allocated for us by
81    // SecKeychainItemCopyContent, must free later...)
82    SecKeychainAttribute itemAttrs[] = { { kSecLabelItemAttr, 0, NULL } };
83    SecKeychainAttributeList itemAttrList =
84    { sizeof(itemAttrs) / sizeof(itemAttrs[0]), itemAttrs };
85
86    ok_status(SecKeychainItemCopyContent(item, NULL, &itemAttrList,
87        NULL, NULL), "get label");
88
89    ok(itemAttrs[0].data != NULL, "check that attribute data was returned");
90
91    // malloc enough space to hold our new label string
92    // (length = old label string + suffix string + terminating NULL)
93    CFIndex newLen = itemAttrs[0].length + strlen(labelSuffix);
94    char *p = (char*) malloc(newLen);
95    memcpy(p, itemAttrs[0].data, itemAttrs[0].length);
96    memcpy(p + itemAttrs[0].length, labelSuffix, strlen(labelSuffix));
97
98    // set up the attribute we want to change with its new value
99    SecKeychainAttribute newAttrs[] = { { kSecLabelItemAttr, newLen, p } };
100    SecKeychainAttributeList newAttrList =
101    { sizeof(newAttrs) / sizeof(newAttrs[0]), newAttrs };
102
103    // modify the attribute
104    ok_status(SecKeychainItemModifyContent(item, &newAttrList,
105        0, NULL), "modify label");
106
107    // free the memory we allocated for the new label string
108    free(p);
109
110    // free the memory in the itemAttrList structure which was
111    // allocated by SecKeychainItemCopyContent
112    ok_status(SecKeychainItemFreeContent(&itemAttrList, NULL),
113        "SecKeychainItemFreeContent");
114}
115
116void testRenameItemLabels(SecKeychainRef keychain)
117{
118    // Find each generic password item in the given keychain whose label
119    // is "sample service", and modify the existing label attribute
120    // by adding a " [label]" suffix.
121
122	const char *searchString = "sample service";
123
124	SecKeychainSearchRef searchRef = nil;
125	SecKeychainAttribute sAttrs[] =
126	{ { kSecServiceItemAttr, strlen(searchString), (char*)searchString } };
127	SecKeychainAttributeList sAttrList =
128	{ sizeof(sAttrs) / sizeof(sAttrs[0]), sAttrs };
129	ok_status(SecKeychainSearchCreateFromAttributes(keychain,
130		kSecGenericPasswordItemClass, &sAttrList, &searchRef),
131		"SecKeychainSearchCreateFromAttributes");
132
133	SecKeychainItemRef foundItemRef = NULL;
134	int count;
135	for (count = 0; count < 2; ++count)
136	{
137		ok_status(SecKeychainSearchCopyNext(searchRef, &foundItemRef),
138			"SecKeychainSearchCopyNext");
139
140        renameItemViaModifyAttributesAndData(foundItemRef); // 4
141        renameItemViaModifyContent(foundItemRef); // 4
142
143		if (foundItemRef)
144			CFRelease(foundItemRef);
145	}
146
147	is_status(SecKeychainSearchCopyNext(searchRef, &foundItemRef),
148		errSecItemNotFound, "SecKeychainSearchCopyNext at end");
149
150	if (searchRef) CFRelease(searchRef);
151}
152
153SecAccessRef createAccess(NSString *accessLabel, BOOL allowAny)
154{
155	SecAccessRef access=nil;
156	NSArray *trustedApplications=nil;
157	
158	if (!allowAny) // use default access ("confirm access")
159	{
160		// make an exception list of applications you want to trust, which
161		// are allowed to access the item without requiring user confirmation
162		SecTrustedApplicationRef myself, someOther;
163		ok_status(SecTrustedApplicationCreateFromPath(NULL, &myself),
164			"create trusted app for self");
165		ok_status(SecTrustedApplicationCreateFromPath("/Applications/Mail.app",
166			&someOther), "create trusted app for Mail.app");
167		trustedApplications = [NSArray arrayWithObjects:(id)myself,
168			(id)someOther, nil];
169		CFRelease(myself);
170		CFRelease(someOther);
171	}
172
173	ok_status(SecAccessCreate((CFStringRef)accessLabel,
174		(CFArrayRef)trustedApplications, &access), "SecAccessCreate");
175
176	if (allowAny)
177	{
178		// change access to be wide-open for decryption ("always allow access")
179		// get the access control list for decryption operations
180		CFArrayRef aclList=nil;
181		ok_status(SecAccessCopySelectedACLList(access,
182			CSSM_ACL_AUTHORIZATION_DECRYPT, &aclList),
183			"SecAccessCopySelectedACLList");
184		
185		// get the first entry in the access control list
186		SecACLRef aclRef=(SecACLRef)CFArrayGetValueAtIndex(aclList, 0);
187		CFArrayRef appList=nil;
188		CFStringRef promptDescription=nil;
189		CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR promptSelector;
190		ok_status(SecACLCopySimpleContents(aclRef, &appList,
191			&promptDescription, &promptSelector), "SecACLCopySimpleContents");
192
193		// modify the default ACL to not require the passphrase, and have a
194		// nil application list
195		promptSelector.flags &= ~CSSM_ACL_KEYCHAIN_PROMPT_REQUIRE_PASSPHRASE;
196		ok_status(SecACLSetSimpleContents(aclRef, NULL, promptDescription,
197			&promptSelector), "SecACLSetSimpleContents");
198
199		if (appList) CFRelease(appList);
200		if (promptDescription) CFRelease(promptDescription);
201		if (aclList) CFRelease(aclList);
202	}
203
204	return access;
205}
206
207void addApplicationPassword(SecKeychainRef keychain, NSString *password,
208	NSString *account, NSString *service, BOOL allowAny, SecKeychainItemRef *outItem)
209{
210    SecKeychainItemRef item = nil;
211    const char *serviceUTF8 = [service UTF8String];
212    const char *accountUTF8 = [account UTF8String];
213    const char *passwordUTF8 = [password UTF8String];
214	// use the service string as the name of this item for display purposes
215	NSString *itemLabel = service;
216	const char *itemLabelUTF8 = [itemLabel UTF8String];
217
218#if USE_SYSTEM_KEYCHAIN
219	const char *sysKeychainPath = "/Library/Keychains/System.keychain";	
220	status = SecKeychainOpen(sysKeychainPath, &keychain);
221	if (status) { NSLog(@"SecKeychainOpen returned %d", status); return; }
222	status = SecKeychainSetPreferenceDomain(kSecPreferencesDomainSystem);
223	if (status) { NSLog(@"SecKeychainSetPreferenceDomain returned %d", status); return; }
224#endif
225
226	// create initial access control settings for the item
227	SecAccessRef access = createAccess(itemLabel, allowAny);
228
229    // Below is the lower-layer equivalent to the
230    // SecKeychainAddGenericPassword() function; it does the same thing
231    // (except specify the access controls) set up attribute vector
232    // (each attribute consists of {tag, length, pointer})
233	SecKeychainAttribute attrs[] = {
234		{ kSecLabelItemAttr, strlen(itemLabelUTF8), (char *)itemLabelUTF8 },
235		{ kSecAccountItemAttr, strlen(accountUTF8), (char *)accountUTF8 },
236		{ kSecServiceItemAttr, strlen(serviceUTF8), (char *)serviceUTF8 }
237	};
238	SecKeychainAttributeList attributes =
239	{ sizeof(attrs) / sizeof(attrs[0]), attrs };
240
241	ok_status(SecKeychainItemCreateFromContent(kSecGenericPasswordItemClass,
242		&attributes, strlen(passwordUTF8), passwordUTF8, keychain, access,
243		&item), "SecKeychainItemCreateFromContent");
244
245	if (access) CFRelease(access);
246
247    if (outItem) {
248        *outItem = item;
249    } else if (item) {
250        CFRelease(item);
251    }
252}
253
254// 1
255void addInternetPassword(SecKeychainRef keychain, NSString *password,
256	NSString *account, NSString *server, NSString *path,
257	SecProtocolType protocol, int port, BOOL allowAny, SecKeychainItemRef *outItem)
258{
259    SecKeychainItemRef item = nil;
260	const char *pathUTF8 = [path UTF8String];
261    const char *serverUTF8 = [server UTF8String];
262    const char *accountUTF8 = [account UTF8String];
263    const char *passwordUTF8 = [password UTF8String];
264	// use the server string as the name of this item for display purposes
265	NSString *itemLabel = server;
266	const char *itemLabelUTF8 = [itemLabel UTF8String];
267
268#if USE_SYSTEM_KEYCHAIN
269	const char *sysKeychainPath = "/Library/Keychains/System.keychain";
270	status = SecKeychainOpen(sysKeychainPath, &keychain);
271	if (status) { NSLog(@"SecKeychainOpen returned %d", status); return 1; }
272	status = SecKeychainSetPreferenceDomain(kSecPreferencesDomainSystem);
273	if (status) { NSLog(@"SecKeychainSetPreferenceDomain returned %d", status); return 1; }
274#endif
275
276	// create initial access control settings for the item
277	SecAccessRef access = createAccess(itemLabel, allowAny);
278
279    // below is the lower-layer equivalent to the
280    // SecKeychainAddInternetPassword() function; it does the same
281    // thing (except specify the access controls) set up attribute
282    // vector (each attribute consists of {tag, length, pointer})
283	SecKeychainAttribute attrs[] = {
284		{ kSecLabelItemAttr, strlen(itemLabelUTF8), (char *)itemLabelUTF8 },
285		{ kSecAccountItemAttr, strlen(accountUTF8), (char *)accountUTF8 },
286		{ kSecServerItemAttr, strlen(serverUTF8), (char *)serverUTF8 },
287		{ kSecPortItemAttr, sizeof(int), (int *)&port },
288		{ kSecProtocolItemAttr, sizeof(SecProtocolType),
289			(SecProtocolType *)&protocol },
290		{ kSecPathItemAttr, strlen(pathUTF8), (char *)pathUTF8 }
291	};
292	SecKeychainAttributeList attributes =
293	{ sizeof(attrs) / sizeof(attrs[0]), attrs };
294
295	ok_status(SecKeychainItemCreateFromContent(kSecInternetPasswordItemClass,
296		&attributes, strlen(passwordUTF8), passwordUTF8, keychain, access,
297		&item), "SecKeychainItemCreateFromContent");
298
299	if (access) CFRelease(access);
300
301    if (outItem) {
302        *outItem = item;
303    } else if (item) {
304        CFRelease(item);
305    }
306}
307
308void tests(void)
309{
310	SecKeychainRef keychain = NULL;
311	ok_status(SecKeychainCreate("login.keychain", 4, "test", NO, NULL,
312		&keychain), "SecKeychainCreate");
313
314	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
315
316	// add some example passwords to the keychain
317	addApplicationPassword(keychain, @"sample password",
318		@"sample account", @"sample service", NO, NULL);
319	addApplicationPassword(keychain, @"sample password",
320		@"different account", @"sample service", NO, NULL);
321	addApplicationPassword(keychain, @"sample password",
322		@"sample account", @"sample unprotected service", YES, NULL);
323	addInternetPassword(keychain, @"sample password",
324		@"sample account", @"samplehost.apple.com",
325		@"cgi-bin/bogus/testpath", kSecProtocolTypeHTTP, 8080, NO, NULL);
326
327	// test searching and changing item label attributes
328	testRenameItemLabels(keychain);
329
330	[pool release];
331
332	SKIP: {
333		skip("no keychain", 1, keychain);
334	    ok_status(SecKeychainDelete(keychain), "SecKeychainDelete");
335	    CFRelease(keychain);
336	}
337
338	tests_end(1);
339}
340
341int main(int argc, char * const *argv)
342{
343	plan_tests(40);
344	tests_begin(argc, argv);
345
346	tests();
347
348	ok_leaks("leaks");
349
350	return 0;
351}
352