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