]> git.saurik.com Git - apple/security.git/blob - OSX/libsecurity_keychain/lib/StorageManager.cpp
Security-57336.1.9.tar.gz
[apple/security.git] / OSX / libsecurity_keychain / lib / StorageManager.cpp
1 /*
2 * Copyright (c) 2000-2004,2011-2014 Apple Inc. All Rights Reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24
25 /*
26 File: StorageManager.cpp
27
28 Contains: Working with multiple keychains
29
30 */
31
32 #include "StorageManager.h"
33 #include "KCEventNotifier.h"
34
35 #include <Security/cssmapple.h>
36 #include <sys/types.h>
37 #include <sys/param.h>
38 #include <syslog.h>
39 #include <pwd.h>
40 #include <algorithm>
41 #include <string>
42 #include <stdio.h>
43 //#include <Security/AuthorizationTags.h>
44 //#include <Security/AuthSession.h>
45 #include <security_utilities/debugging.h>
46 #include <security_keychain/SecCFTypes.h>
47 //#include <Security/SecurityAgentClient.h>
48 #include <securityd_client/ssclient.h>
49 #include <Security/AuthorizationTags.h>
50 #include <Security/AuthorizationTagsPriv.h>
51 #include <Security/SecTask.h>
52 #include <security_keychain/SecCFTypes.h>
53 #include "TrustSettingsSchema.h"
54 #include <security_cdsa_client/wrapkey.h>
55
56 //%%% add this to AuthorizationTagsPriv.h later
57 #ifndef AGENT_HINT_LOGIN_KC_SUPPRESS_RESET_PANEL
58 #define AGENT_HINT_LOGIN_KC_SUPPRESS_RESET_PANEL "loginKCCreate:suppressResetPanel"
59 #endif
60
61 #include "KCCursor.h"
62 #include "Globals.h"
63
64
65 using namespace CssmClient;
66 using namespace KeychainCore;
67
68 #define kLoginKeychainPathPrefix "~/Library/Keychains/"
69 #define kUserLoginKeychainPath "~/Library/Keychains/login.keychain"
70 #define kEmptyKeychainSizeInBytes 20460
71
72 //-----------------------------------------------------------------------------------
73
74 static SecPreferencesDomain defaultPreferenceDomain()
75 {
76 SessionAttributeBits sessionAttrs;
77 if (gServerMode) {
78 secdebug("servermode", "StorageManager initialized in server mode");
79 sessionAttrs = sessionIsRoot;
80 } else {
81 MacOSError::check(SessionGetInfo(callerSecuritySession, NULL, &sessionAttrs));
82 }
83
84 // If this is the root session, use system preferences.
85 // (In SecurityServer debug mode, you'll get a (fake) root session
86 // that has graphics access. Ignore that to help testing.)
87 if ((sessionAttrs & sessionIsRoot)
88 IFDEBUG( && !(sessionAttrs & sessionHasGraphicAccess))) {
89 secdebug("storagemgr", "using system preferences");
90 return kSecPreferencesDomainSystem;
91 }
92
93 // otherwise, use normal (user) preferences
94 return kSecPreferencesDomainUser;
95 }
96
97 static bool isAppSandboxed()
98 {
99 bool result = false;
100 SecTaskRef task = SecTaskCreateFromSelf(NULL);
101 if(task != NULL) {
102 CFTypeRef appSandboxValue = SecTaskCopyValueForEntitlement(task,
103 CFSTR("com.apple.security.app-sandbox"), NULL);
104 if(appSandboxValue != NULL) {
105 result = true;
106 CFRelease(appSandboxValue);
107 }
108 CFRelease(task);
109 }
110 return result;
111 }
112
113 static bool shouldAddToSearchList(const DLDbIdentifier &dLDbIdentifier)
114 {
115 // Creation of a private keychain should not modify the search list: rdar://13529331
116 // However, we want to ensure the login and System keychains are in
117 // the search list if that is not the case when they are created.
118 // Note that App Sandbox apps may not modify the list in either case.
119
120 bool loginOrSystemKeychain = false;
121 const char *dbname = dLDbIdentifier.dbName();
122 if (dbname) {
123 if ((!strcmp(dbname, "/Library/Keychains/System.keychain")) ||
124 (strstr(dbname, "/login.keychain")) ) {
125 loginOrSystemKeychain = true;
126 }
127 }
128 return (loginOrSystemKeychain && !isAppSandboxed());
129 }
130
131
132 StorageManager::StorageManager() :
133 mSavedList(defaultPreferenceDomain()),
134 mCommonList(kSecPreferencesDomainCommon),
135 mDomain(kSecPreferencesDomainUser),
136 mMutex(Mutex::recursive)
137 {
138 }
139
140
141 Mutex*
142 StorageManager::getStorageManagerMutex()
143 {
144 return &mKeychainMapMutex;
145 }
146
147
148 Keychain
149 StorageManager::keychain(const DLDbIdentifier &dLDbIdentifier)
150 {
151 StLock<Mutex>_(mKeychainMapMutex);
152
153 if (!dLDbIdentifier)
154 return Keychain();
155
156 KeychainMap::iterator it = mKeychains.find(dLDbIdentifier);
157 if (it != mKeychains.end())
158 {
159 if (it->second == NULL) // cleared by weak reference?
160 {
161 mKeychains.erase(it);
162 }
163 else
164 {
165 return it->second;
166 }
167 }
168
169 if (gServerMode) {
170 secdebug("servermode", "keychain reference in server mode");
171 return Keychain();
172 }
173
174 // The keychain is not in our cache. Create it.
175 Module module(dLDbIdentifier.ssuid().guid());
176 DL dl;
177 if (dLDbIdentifier.ssuid().subserviceType() & CSSM_SERVICE_CSP)
178 dl = SSCSPDL(module);
179 else
180 dl = DL(module);
181
182 dl->subserviceId(dLDbIdentifier.ssuid().subserviceId());
183 dl->version(dLDbIdentifier.ssuid().version());
184 Db db(dl, dLDbIdentifier.dbName());
185
186 Keychain keychain(db);
187 // Add the keychain to the cache.
188 mKeychains.insert(KeychainMap::value_type(dLDbIdentifier, &*keychain));
189 keychain->inCache(true);
190
191 return keychain;
192 }
193
194 void
195 StorageManager::removeKeychain(const DLDbIdentifier &dLDbIdentifier,
196 KeychainImpl *keychainImpl)
197 {
198 // Lock the recursive mutex
199
200 StLock<Mutex>_(mKeychainMapMutex);
201
202 KeychainMap::iterator it = mKeychains.find(dLDbIdentifier);
203 if (it != mKeychains.end() && (KeychainImpl*) it->second == keychainImpl)
204 mKeychains.erase(it);
205
206 keychainImpl->inCache(false);
207 }
208
209 void
210 StorageManager::didRemoveKeychain(const DLDbIdentifier &dLDbIdentifier)
211 {
212 // Lock the recursive mutex
213
214 StLock<Mutex>_(mKeychainMapMutex);
215
216 KeychainMap::iterator it = mKeychains.find(dLDbIdentifier);
217 if (it != mKeychains.end())
218 {
219 if (it->second != NULL) // did we get zapped by weak reference destruction
220 {
221 KeychainImpl *keychainImpl = it->second;
222 keychainImpl->inCache(false);
223 }
224
225 mKeychains.erase(it);
226 }
227 }
228
229 // Create keychain if it doesn't exist, and optionally add it to the search list.
230 Keychain
231 StorageManager::makeKeychain(const DLDbIdentifier &dLDbIdentifier, bool add)
232 {
233 StLock<Mutex>_(mKeychainMapMutex);
234
235 Keychain theKeychain = keychain(dLDbIdentifier);
236 bool post = false;
237 bool updateList = (add && shouldAddToSearchList(dLDbIdentifier));
238
239 if (updateList)
240 {
241 mSavedList.revert(false);
242 DLDbList searchList = mSavedList.searchList();
243 if (find(searchList.begin(), searchList.end(), dLDbIdentifier) != searchList.end())
244 return theKeychain; // theKeychain is already in the searchList.
245
246 mCommonList.revert(false);
247 searchList = mCommonList.searchList();
248 if (find(searchList.begin(), searchList.end(), dLDbIdentifier) != searchList.end())
249 return theKeychain; // theKeychain is already in the commonList don't add it to the searchList.
250
251 // If theKeychain doesn't exist don't bother adding it to the search list yet.
252 if (!theKeychain->exists())
253 return theKeychain;
254
255 // theKeychain exists and is not in our search list, so add it to the
256 // search list.
257 mSavedList.revert(true);
258 mSavedList.add(dLDbIdentifier);
259 mSavedList.save();
260 post = true;
261 }
262
263 if (post)
264 {
265 // Make sure we are not holding mStorageManagerLock anymore when we
266 // post this event.
267 KCEventNotifier::PostKeychainEvent(kSecKeychainListChangedEvent);
268 }
269
270 return theKeychain;
271 }
272
273 // Be notified a Keychain just got created.
274 void
275 StorageManager::created(const Keychain &keychain)
276 {
277 StLock<Mutex>_(mKeychainMapMutex);
278
279 DLDbIdentifier dLDbIdentifier = keychain->dlDbIdentifier();
280 bool defaultChanged = false;
281 bool updateList = shouldAddToSearchList(dLDbIdentifier);
282
283 if (updateList)
284 {
285 mSavedList.revert(true);
286 // If we don't have a default Keychain yet. Make the newly created
287 // keychain the default.
288 if (!mSavedList.defaultDLDbIdentifier())
289 {
290 mSavedList.defaultDLDbIdentifier(dLDbIdentifier);
291 defaultChanged = true;
292 }
293
294 // Add the keychain to the search list prefs.
295 mSavedList.add(dLDbIdentifier);
296 mSavedList.save();
297
298 // Make sure we are not holding mLock when we post these events.
299 KCEventNotifier::PostKeychainEvent(kSecKeychainListChangedEvent);
300 }
301
302 if (defaultChanged)
303 {
304 KCEventNotifier::PostKeychainEvent(kSecDefaultChangedEvent, dLDbIdentifier);
305 }
306 }
307
308 KCCursor
309 StorageManager::createCursor(SecItemClass itemClass,
310 const SecKeychainAttributeList *attrList)
311 {
312 StLock<Mutex>_(mMutex);
313
314 KeychainList searchList;
315 getSearchList(searchList);
316 return KCCursor(searchList, itemClass, attrList);
317 }
318
319 KCCursor
320 StorageManager::createCursor(const SecKeychainAttributeList *attrList)
321 {
322 StLock<Mutex>_(mMutex);
323
324 KeychainList searchList;
325 getSearchList(searchList);
326 return KCCursor(searchList, attrList);
327 }
328
329 void
330 StorageManager::lockAll()
331 {
332 StLock<Mutex>_(mMutex);
333
334 SecurityServer::ClientSession ss(Allocator::standard(), Allocator::standard());
335 ss.lockAll (false);
336 }
337
338 Keychain
339 StorageManager::defaultKeychain()
340 {
341 StLock<Mutex>_(mMutex);
342
343 Keychain theKeychain;
344 CFTypeRef ref;
345
346 {
347 mSavedList.revert(false);
348 DLDbIdentifier defaultDLDbIdentifier(mSavedList.defaultDLDbIdentifier());
349 if (defaultDLDbIdentifier)
350 {
351 theKeychain = keychain(defaultDLDbIdentifier);
352 ref = theKeychain->handle(false);
353 }
354 }
355
356 if (theKeychain /* && theKeychain->exists() */)
357 return theKeychain;
358
359 MacOSError::throwMe(errSecNoDefaultKeychain);
360 }
361
362 void
363 StorageManager::defaultKeychain(const Keychain &keychain)
364 {
365 StLock<Mutex>_(mMutex);
366
367 // Only set a keychain as the default if we own it and can read/write it,
368 // and our uid allows modifying the directory for that preference domain.
369 if (!keychainOwnerPermissionsValidForDomain(keychain->name(), mDomain))
370 MacOSError::throwMe(errSecWrPerm);
371
372 DLDbIdentifier oldDefaultId;
373 DLDbIdentifier newDefaultId(keychain->dlDbIdentifier());
374 {
375 oldDefaultId = mSavedList.defaultDLDbIdentifier();
376 mSavedList.revert(true);
377 mSavedList.defaultDLDbIdentifier(newDefaultId);
378 mSavedList.save();
379 }
380
381 if (!(oldDefaultId == newDefaultId))
382 {
383 // Make sure we are not holding mLock when we post this event.
384 KCEventNotifier::PostKeychainEvent(kSecDefaultChangedEvent, newDefaultId);
385 }
386 }
387
388 Keychain
389 StorageManager::defaultKeychain(SecPreferencesDomain domain)
390 {
391 StLock<Mutex>_(mMutex);
392
393 if (domain == kSecPreferencesDomainDynamic)
394 MacOSError::throwMe(errSecInvalidPrefsDomain);
395
396 if (domain == mDomain)
397 return defaultKeychain();
398 else
399 {
400 DLDbIdentifier defaultDLDbIdentifier(DLDbListCFPref(domain).defaultDLDbIdentifier());
401 if (defaultDLDbIdentifier)
402 return keychain(defaultDLDbIdentifier);
403
404 MacOSError::throwMe(errSecNoDefaultKeychain);
405 }
406 }
407
408 void
409 StorageManager::defaultKeychain(SecPreferencesDomain domain, const Keychain &keychain)
410 {
411 StLock<Mutex>_(mMutex);
412
413 if (domain == kSecPreferencesDomainDynamic)
414 MacOSError::throwMe(errSecInvalidPrefsDomain);
415
416 if (domain == mDomain)
417 defaultKeychain(keychain);
418 else
419 DLDbListCFPref(domain).defaultDLDbIdentifier(keychain->dlDbIdentifier());
420 }
421
422 Keychain
423 StorageManager::loginKeychain()
424 {
425 StLock<Mutex>_(mMutex);
426
427 Keychain theKeychain;
428 {
429 mSavedList.revert(false);
430 DLDbIdentifier loginDLDbIdentifier(mSavedList.loginDLDbIdentifier());
431 if (loginDLDbIdentifier)
432 {
433 theKeychain = keychain(loginDLDbIdentifier);
434 }
435 }
436
437 if (theKeychain && theKeychain->exists())
438 return theKeychain;
439
440 MacOSError::throwMe(errSecNoSuchKeychain);
441 }
442
443 void
444 StorageManager::loginKeychain(Keychain keychain)
445 {
446 StLock<Mutex>_(mMutex);
447
448 mSavedList.revert(true);
449 mSavedList.loginDLDbIdentifier(keychain->dlDbIdentifier());
450 mSavedList.save();
451 }
452
453 size_t
454 StorageManager::size()
455 {
456 StLock<Mutex>_(mMutex);
457
458 mSavedList.revert(false);
459 mCommonList.revert(false);
460 return mSavedList.searchList().size() + mCommonList.searchList().size();
461 }
462
463 Keychain
464 StorageManager::at(unsigned int ix)
465 {
466 StLock<Mutex>_(mMutex);
467
468 mSavedList.revert(false);
469 DLDbList dLDbList = mSavedList.searchList();
470 if (ix < dLDbList.size())
471 {
472 return keychain(dLDbList[ix]);
473 }
474 else
475 {
476 ix -= dLDbList.size();
477 mCommonList.revert(false);
478 DLDbList commonList = mCommonList.searchList();
479 if (ix >= commonList.size())
480 MacOSError::throwMe(errSecInvalidKeychain);
481
482 return keychain(commonList[ix]);
483 }
484 }
485
486 Keychain
487 StorageManager::operator[](unsigned int ix)
488 {
489 StLock<Mutex>_(mMutex);
490
491 return at(ix);
492 }
493
494 void StorageManager::rename(Keychain keychain, const char* newName)
495 {
496
497 StLock<Mutex>_(mKeychainMapMutex);
498
499 bool changedDefault = false;
500 DLDbIdentifier newDLDbIdentifier;
501 {
502 mSavedList.revert(true);
503 DLDbIdentifier defaultId = mSavedList.defaultDLDbIdentifier();
504
505 // Find the keychain object for the given ref
506 DLDbIdentifier dLDbIdentifier = keychain->dlDbIdentifier();
507
508 // Actually rename the database on disk.
509 keychain->database()->rename(newName);
510
511 if (dLDbIdentifier == defaultId)
512 changedDefault=true;
513
514 newDLDbIdentifier = keychain->dlDbIdentifier();
515 // Rename the keychain in the search list.
516 mSavedList.rename(dLDbIdentifier, newDLDbIdentifier);
517
518 // If this was the default keychain change it accordingly
519 if (changedDefault)
520 mSavedList.defaultDLDbIdentifier(newDLDbIdentifier);
521
522 mSavedList.save();
523
524 // we aren't worried about a weak reference here, because we have to
525 // hold a lock on an item in order to do the rename
526
527 // Now update the Keychain cache
528 if (keychain->inCache())
529 {
530 KeychainMap::iterator it = mKeychains.find(dLDbIdentifier);
531 if (it != mKeychains.end() && (KeychainImpl*) it->second == keychain.get())
532 {
533 // Remove the keychain from the cache under its old
534 // dLDbIdentifier
535 mKeychains.erase(it);
536 }
537 }
538
539 // If we renamed this keychain on top of an existing one we should
540 // drop the old one from the cache.
541 KeychainMap::iterator it = mKeychains.find(newDLDbIdentifier);
542 if (it != mKeychains.end())
543 {
544 Keychain oldKeychain(it->second);
545 oldKeychain->inCache(false);
546 // @@@ Ideally we should invalidate or fault this keychain object.
547 }
548
549 if (keychain->inCache())
550 {
551 // If the keychain wasn't in the cache to being with let's not put
552 // it there now. There was probably a good reason it wasn't in it.
553 // If the keychain was in the cache, update it to use
554 // newDLDbIdentifier.
555 mKeychains.insert(KeychainMap::value_type(newDLDbIdentifier,
556 keychain));
557 }
558 }
559
560 // Make sure we are not holding mLock when we post these events.
561 KCEventNotifier::PostKeychainEvent(kSecKeychainListChangedEvent);
562
563 if (changedDefault)
564 KCEventNotifier::PostKeychainEvent(kSecDefaultChangedEvent,
565 newDLDbIdentifier);
566 }
567
568 void StorageManager::renameUnique(Keychain keychain, CFStringRef newName)
569 {
570 StLock<Mutex>_(mMutex);
571
572 bool doneCreating = false;
573 int index = 1;
574 do
575 {
576 char newNameCString[MAXPATHLEN];
577 if ( CFStringGetCString(newName, newNameCString, MAXPATHLEN, kCFStringEncodingUTF8) ) // make sure it fits in MAXPATHLEN, etc.
578 {
579 // Construct the new name...
580 //
581 CFMutableStringRef newNameCFStr = NULL;
582 newNameCFStr = CFStringCreateMutable(NULL, MAXPATHLEN);
583 if ( newNameCFStr )
584 {
585 CFStringAppendFormat(newNameCFStr, NULL, CFSTR("%s%d"), newNameCString, index);
586 CFStringAppend(newNameCFStr, CFSTR(kKeychainSuffix)); // add .keychain
587 char toUseBuff2[MAXPATHLEN];
588 if ( CFStringGetCString(newNameCFStr, toUseBuff2, MAXPATHLEN, kCFStringEncodingUTF8) ) // make sure it fits in MAXPATHLEN, etc.
589 {
590 struct stat filebuf;
591 if ( lstat(toUseBuff2, &filebuf) )
592 {
593 rename(keychain, toUseBuff2);
594 KeychainList kcList;
595 kcList.push_back(keychain);
596 remove(kcList, false);
597 doneCreating = true;
598 }
599 else
600 index++;
601 }
602 else
603 doneCreating = true; // failure to get c string.
604 CFRelease(newNameCFStr);
605 }
606 else
607 doneCreating = false; // failure to create mutable string.
608 }
609 else
610 doneCreating = false; // failure to get the string (i.e. > MAXPATHLEN?)
611 }
612 while (!doneCreating && index != INT_MAX);
613 }
614
615 #define KEYCHAIN_SYNC_KEY CFSTR("KeychainSyncList")
616 #define KEYCHAIN_SYNC_DOMAIN CFSTR("com.apple.keychainsync")
617
618 static CFStringRef MakeExpandedPath (const char* path)
619 {
620 std::string name = DLDbListCFPref::ExpandTildesInPath (std::string (path));
621 CFStringRef expanded = CFStringCreateWithCString (NULL, name.c_str (), 0);
622 return expanded;
623 }
624
625 void StorageManager::removeKeychainFromSyncList (const DLDbIdentifier &id)
626 {
627 StLock<Mutex>_(mMutex);
628
629 // make a CFString of our identifier
630 const char* idname = id.dbName ();
631 if (idname == NULL)
632 {
633 return;
634 }
635
636 CFRef<CFStringRef> idString = MakeExpandedPath (idname);
637
638 // check and see if this keychain is in the keychain syncing list
639 CFArrayRef value =
640 (CFArrayRef) CFPreferencesCopyValue (KEYCHAIN_SYNC_KEY,
641 KEYCHAIN_SYNC_DOMAIN,
642 kCFPreferencesCurrentUser,
643 kCFPreferencesAnyHost);
644 if (value == NULL)
645 {
646 return;
647 }
648
649 // make a mutable copy of the dictionary
650 CFRef<CFMutableArrayRef> mtValue = CFArrayCreateMutableCopy (NULL, 0, value);
651 CFRelease (value);
652
653 // walk the array, looking for the value
654 CFIndex i;
655 CFIndex limit = CFArrayGetCount (mtValue.get());
656 bool found = false;
657
658 for (i = 0; i < limit; ++i)
659 {
660 CFDictionaryRef idx = (CFDictionaryRef) CFArrayGetValueAtIndex (mtValue.get(), i);
661 CFStringRef v = (CFStringRef) CFDictionaryGetValue (idx, CFSTR("DbName"));
662 if (v == NULL)
663 {
664 return; // something is really wrong if this is taken
665 }
666
667 char* stringBuffer = NULL;
668 const char* pathString = CFStringGetCStringPtr(v, 0);
669 if (pathString == 0)
670 {
671 CFIndex maxLen = CFStringGetMaximumSizeForEncoding(CFStringGetLength(v), kCFStringEncodingUTF8) + 1;
672 stringBuffer = (char*) malloc(maxLen);
673 CFStringGetCString(v, stringBuffer, maxLen, kCFStringEncodingUTF8);
674 pathString = stringBuffer;
675 }
676
677 CFStringRef vExpanded = MakeExpandedPath(pathString);
678 CFComparisonResult result = CFStringCompare (vExpanded, idString.get(), 0);
679 if (stringBuffer != NULL)
680 {
681 free(stringBuffer);
682 }
683
684 CFRelease (vExpanded);
685
686 if (result == 0)
687 {
688 CFArrayRemoveValueAtIndex (mtValue.get(), i);
689 found = true;
690 break;
691 }
692 }
693
694 if (found)
695 {
696 #ifndef NDEBUG
697 CFShow (mtValue.get());
698 #endif
699
700 CFPreferencesSetValue (KEYCHAIN_SYNC_KEY,
701 mtValue,
702 KEYCHAIN_SYNC_DOMAIN,
703 kCFPreferencesCurrentUser,
704 kCFPreferencesAnyHost);
705 CFPreferencesSynchronize (KEYCHAIN_SYNC_DOMAIN, kCFPreferencesCurrentUser, kCFPreferencesAnyHost);
706 }
707 }
708
709 void StorageManager::remove(const KeychainList &kcsToRemove, bool deleteDb)
710 {
711 StLock<Mutex>_(mMutex);
712
713 bool unsetDefault = false;
714 bool updateList = (!isAppSandboxed());
715
716 if (updateList)
717 {
718 mSavedList.revert(true);
719 DLDbIdentifier defaultId = mSavedList.defaultDLDbIdentifier();
720 for (KeychainList::const_iterator ix = kcsToRemove.begin();
721 ix != kcsToRemove.end(); ++ix)
722 {
723 // Find the keychain object for the given ref
724 Keychain theKeychain = *ix;
725 DLDbIdentifier dLDbIdentifier = theKeychain->dlDbIdentifier();
726
727 // Remove it from the saved list
728 mSavedList.remove(dLDbIdentifier);
729 if (dLDbIdentifier == defaultId)
730 unsetDefault=true;
731
732 if (deleteDb)
733 {
734 removeKeychainFromSyncList (dLDbIdentifier);
735
736 // Now remove it from the cache
737 removeKeychain(dLDbIdentifier, theKeychain.get());
738 }
739 }
740
741 if (unsetDefault)
742 mSavedList.defaultDLDbIdentifier(DLDbIdentifier());
743
744 mSavedList.save();
745 }
746
747 if (deleteDb)
748 {
749 // Delete the actual databases without holding any locks.
750 for (KeychainList::const_iterator ix = kcsToRemove.begin();
751 ix != kcsToRemove.end(); ++ix)
752 {
753 (*ix)->database()->deleteDb();
754 }
755 }
756
757 if (updateList) {
758 // Make sure we are not holding mLock when we post these events.
759 KCEventNotifier::PostKeychainEvent(kSecKeychainListChangedEvent);
760 }
761
762 if (unsetDefault)
763 KCEventNotifier::PostKeychainEvent(kSecDefaultChangedEvent);
764 }
765
766 void
767 StorageManager::getSearchList(KeychainList &keychainList)
768 {
769 // hold the global lock since we make keychain objects in this function
770
771 // to do: each of the items in this list must be retained, otherwise mayhem will occur
772 StLock<Mutex>_(mMutex);
773
774 if (gServerMode) {
775 keychainList.clear();
776 return;
777 }
778
779 mSavedList.revert(false);
780 mCommonList.revert(false);
781
782 // Merge mSavedList, mDynamicList and mCommonList
783 DLDbList dLDbList = mSavedList.searchList();
784 DLDbList dynamicList = mDynamicList.searchList();
785 DLDbList commonList = mCommonList.searchList();
786 KeychainList result;
787 result.reserve(dLDbList.size() + dynamicList.size() + commonList.size());
788
789 {
790 for (DLDbList::const_iterator it = dynamicList.begin();
791 it != dynamicList.end(); ++it)
792 {
793 Keychain k = keychain(*it);
794 result.push_back(k);
795 }
796
797 for (DLDbList::const_iterator it = dLDbList.begin();
798 it != dLDbList.end(); ++it)
799 {
800 Keychain k = keychain(*it);
801 result.push_back(k);
802 }
803
804 for (DLDbList::const_iterator it = commonList.begin();
805 it != commonList.end(); ++it)
806 {
807 Keychain k = keychain(*it);
808 result.push_back(k);
809 }
810 }
811
812 keychainList.swap(result);
813 }
814
815 void
816 StorageManager::setSearchList(const KeychainList &keychainList)
817 {
818 StLock<Mutex>_(mMutex);
819
820 DLDbList commonList = mCommonList.searchList();
821
822 // Strip out the common list part from the end of the search list.
823 KeychainList::const_iterator it_end = keychainList.end();
824 DLDbList::const_reverse_iterator end_common = commonList.rend();
825 for (DLDbList::const_reverse_iterator it_common = commonList.rbegin(); it_common != end_common; ++it_common)
826 {
827 // Eliminate common entries from the end of the passed in keychainList.
828 if (it_end == keychainList.begin())
829 break;
830
831 --it_end;
832 if (!((*it_end)->dlDbIdentifier() == *it_common))
833 {
834 ++it_end;
835 break;
836 }
837 }
838
839 /* it_end now points one past the last element in keychainList which is not in commonList. */
840 DLDbList searchList, oldSearchList(mSavedList.searchList());
841 for (KeychainList::const_iterator it = keychainList.begin(); it != it_end; ++it)
842 {
843 searchList.push_back((*it)->dlDbIdentifier());
844 }
845
846 {
847 // Set the current searchlist to be what was passed in, the old list will be freed
848 // upon exit of this stackframe.
849 mSavedList.revert(true);
850 mSavedList.searchList(searchList);
851 mSavedList.save();
852 }
853
854 if (!(oldSearchList == searchList))
855 {
856 // Make sure we are not holding mLock when we post this event.
857 KCEventNotifier::PostKeychainEvent(kSecKeychainListChangedEvent);
858 }
859 }
860
861 void
862 StorageManager::getSearchList(SecPreferencesDomain domain, KeychainList &keychainList)
863 {
864 StLock<Mutex>_(mMutex);
865
866 if (gServerMode) {
867 keychainList.clear();
868 return;
869 }
870
871 if (domain == kSecPreferencesDomainDynamic)
872 {
873 convertList(keychainList, mDynamicList.searchList());
874 }
875 else if (domain == mDomain)
876 {
877 mSavedList.revert(false);
878 convertList(keychainList, mSavedList.searchList());
879 }
880 else
881 {
882 convertList(keychainList, DLDbListCFPref(domain).searchList());
883 }
884 }
885
886 void StorageManager::forceUserSearchListReread()
887 {
888 mSavedList.forceUserSearchListReread();
889 }
890
891 void
892 StorageManager::setSearchList(SecPreferencesDomain domain, const KeychainList &keychainList)
893 {
894 StLock<Mutex>_(mMutex);
895
896 if (domain == kSecPreferencesDomainDynamic)
897 MacOSError::throwMe(errSecInvalidPrefsDomain);
898
899 DLDbList searchList;
900 convertList(searchList, keychainList);
901
902 if (domain == mDomain)
903 {
904 DLDbList oldSearchList(mSavedList.searchList());
905 {
906 // Set the current searchlist to be what was passed in, the old list will be freed
907 // upon exit of this stackframe.
908 mSavedList.revert(true);
909 mSavedList.searchList(searchList);
910 mSavedList.save();
911 }
912
913 if (!(oldSearchList == searchList))
914 {
915 KCEventNotifier::PostKeychainEvent(kSecKeychainListChangedEvent);
916 }
917 }
918 else
919 {
920 DLDbListCFPref(domain).searchList(searchList);
921 }
922 }
923
924 void
925 StorageManager::domain(SecPreferencesDomain domain)
926 {
927 StLock<Mutex>_(mMutex);
928
929 if (domain == kSecPreferencesDomainDynamic)
930 MacOSError::throwMe(errSecInvalidPrefsDomain);
931
932 if (domain == mDomain)
933 return; // no change
934
935 #if !defined(NDEBUG)
936 switch (domain)
937 {
938 case kSecPreferencesDomainSystem:
939 secdebug("storagemgr", "switching to system domain"); break;
940 case kSecPreferencesDomainUser:
941 secdebug("storagemgr", "switching to user domain (uid %d)", getuid()); break;
942 default:
943 secdebug("storagemgr", "switching to weird prefs domain %d", domain); break;
944 }
945 #endif
946
947 mDomain = domain;
948 mSavedList.set(domain);
949 }
950
951 void
952 StorageManager::optionalSearchList(CFTypeRef keychainOrArray, KeychainList &keychainList)
953 {
954 StLock<Mutex>_(mMutex);
955
956 if (!keychainOrArray)
957 getSearchList(keychainList);
958 else
959 {
960 CFTypeID typeID = CFGetTypeID(keychainOrArray);
961 if (typeID == CFArrayGetTypeID())
962 convertToKeychainList(CFArrayRef(keychainOrArray), keychainList);
963 else if (typeID == gTypes().KeychainImpl.typeID)
964 keychainList.push_back(KeychainImpl::required(SecKeychainRef(keychainOrArray)));
965 else
966 MacOSError::throwMe(errSecParam);
967 }
968 }
969
970 // static methods.
971 void
972 StorageManager::convertToKeychainList(CFArrayRef keychainArray, KeychainList &keychainList)
973 {
974 CFIndex count = CFArrayGetCount(keychainArray);
975 if (!(count > 0))
976 return;
977
978 KeychainList keychains(count);
979 for (CFIndex ix = 0; ix < count; ++ix)
980 {
981 keychains[ix] = KeychainImpl::required(SecKeychainRef(CFArrayGetValueAtIndex(keychainArray, ix)));
982 }
983
984 keychainList.swap(keychains);
985 }
986
987 CFArrayRef
988 StorageManager::convertFromKeychainList(const KeychainList &keychainList)
989 {
990 CFRef<CFMutableArrayRef> keychainArray(CFArrayCreateMutable(NULL, keychainList.size(), &kCFTypeArrayCallBacks));
991
992 for (KeychainList::const_iterator ix = keychainList.begin(); ix != keychainList.end(); ++ix)
993 {
994 SecKeychainRef keychainRef = (*ix)->handle();
995 CFArrayAppendValue(keychainArray, keychainRef);
996 CFRelease(keychainRef);
997 }
998
999 // Counter the CFRelease that CFRef<> is about to do when keychainArray goes out of scope.
1000 CFRetain(keychainArray);
1001 return keychainArray;
1002 }
1003
1004 void StorageManager::convertList(DLDbList &ids, const KeychainList &kcs)
1005 {
1006 DLDbList result;
1007 result.reserve(kcs.size());
1008 for (KeychainList::const_iterator ix = kcs.begin(); ix != kcs.end(); ++ix)
1009 {
1010 result.push_back((*ix)->dlDbIdentifier());
1011 }
1012 ids.swap(result);
1013 }
1014
1015 void StorageManager::convertList(KeychainList &kcs, const DLDbList &ids)
1016 {
1017 StLock<Mutex>_(mMutex);
1018
1019 KeychainList result;
1020 result.reserve(ids.size());
1021 {
1022 for (DLDbList::const_iterator ix = ids.begin(); ix != ids.end(); ++ix)
1023 result.push_back(keychain(*ix));
1024 }
1025 kcs.swap(result);
1026 }
1027
1028 #pragma mark ____ Login Functions ____
1029
1030 void StorageManager::login(AuthorizationRef authRef, UInt32 nameLength, const char* name)
1031 {
1032 StLock<Mutex>_(mMutex);
1033
1034 AuthorizationItemSet* info = NULL;
1035 OSStatus result = AuthorizationCopyInfo(authRef, NULL, &info); // get the results of the copy rights call.
1036 Boolean created = false;
1037 if ( result == errSecSuccess && info->count )
1038 {
1039 // Grab the password from the auth context (info) and create the keychain...
1040 //
1041 AuthorizationItem* currItem = info->items;
1042 for (UInt32 index = 1; index <= info->count; index++) //@@@plugin bug won't return a specific context.
1043 {
1044 if (strcmp(currItem->name, kAuthorizationEnvironmentPassword) == 0)
1045 {
1046 // creates the login keychain with the specified password
1047 try
1048 {
1049 login(nameLength, name, (UInt32)currItem->valueLength, currItem->value);
1050 created = true;
1051 }
1052 catch(...)
1053 {
1054 }
1055 break;
1056 }
1057 currItem++;
1058 }
1059 }
1060 if ( info )
1061 AuthorizationFreeItemSet(info);
1062
1063 if ( !created )
1064 MacOSError::throwMe(errAuthorizationInternal);
1065 }
1066
1067 void StorageManager::login(ConstStringPtr name, ConstStringPtr password)
1068 {
1069 StLock<Mutex>_(mMutex);
1070
1071 if ( name == NULL || password == NULL )
1072 MacOSError::throwMe(errSecParam);
1073
1074 login(name[0], name + 1, password[0], password + 1);
1075 }
1076
1077 void StorageManager::login(UInt32 nameLength, const void *name,
1078 UInt32 passwordLength, const void *password)
1079 {
1080 if (passwordLength != 0 && password == NULL)
1081 {
1082 secdebug("KCLogin", "StorageManager::login: invalid argument (NULL password)");
1083 MacOSError::throwMe(errSecParam);
1084 }
1085
1086 DLDbIdentifier loginDLDbIdentifier;
1087 {
1088 mSavedList.revert(true);
1089 loginDLDbIdentifier = mSavedList.loginDLDbIdentifier();
1090 }
1091
1092 secdebug("KCLogin", "StorageManager::login: loginDLDbIdentifier is %s", (loginDLDbIdentifier) ? loginDLDbIdentifier.dbName() : "<NULL>");
1093 if (!loginDLDbIdentifier)
1094 MacOSError::throwMe(errSecNoSuchKeychain);
1095
1096
1097 //***************************************************************
1098 // gather keychain information
1099 //***************************************************************
1100
1101 // user name
1102 int uid = geteuid();
1103 struct passwd *pw = getpwuid(uid);
1104 if (pw == NULL) {
1105 secdebug("KCLogin", "StorageManager::login: invalid argument (NULL uid)");
1106 MacOSError::throwMe(errSecParam);
1107 }
1108 char *userName = pw->pw_name;
1109
1110 // make keychain path strings
1111 std::string keychainPath = DLDbListCFPref::ExpandTildesInPath(kLoginKeychainPathPrefix);
1112 std::string shortnameKeychain = keychainPath + userName;
1113 std::string shortnameDotKeychain = shortnameKeychain + ".keychain";
1114 std::string loginDotKeychain = keychainPath + "login.keychain";
1115 std::string loginRenamed1Keychain = keychainPath + "login_renamed1.keychain";
1116
1117 // check for existence of keychain files
1118 bool shortnameKeychainExists = false;
1119 bool shortnameDotKeychainExists = false;
1120 bool loginKeychainExists = false;
1121 bool loginRenamed1KeychainExists = false;
1122 {
1123 struct stat st;
1124 int stat_result;
1125 stat_result = ::stat(shortnameKeychain.c_str(), &st);
1126 shortnameKeychainExists = (stat_result == 0);
1127 stat_result = ::stat(shortnameDotKeychain.c_str(), &st);
1128 shortnameDotKeychainExists = (stat_result == 0);
1129 stat_result = ::stat(loginDotKeychain.c_str(), &st);
1130 loginKeychainExists = (stat_result == 0);
1131 stat_result = ::stat(loginRenamed1Keychain.c_str(), &st);
1132 loginRenamed1KeychainExists = (stat_result == 0);
1133 }
1134
1135 bool loginUnlocked = false;
1136
1137 // make the keychain identifiers
1138 CSSM_VERSION version = {0, 0};
1139 DLDbIdentifier shortnameDLDbIdentifier = DLDbListCFPref::makeDLDbIdentifier(gGuidAppleCSPDL, version, 0, CSSM_SERVICE_CSP | CSSM_SERVICE_DL, shortnameKeychain.c_str(), NULL);
1140 DLDbIdentifier shortnameDotDLDbIdentifier = DLDbListCFPref::makeDLDbIdentifier(gGuidAppleCSPDL, version, 0, CSSM_SERVICE_CSP | CSSM_SERVICE_DL, shortnameDotKeychain.c_str(), NULL);
1141 DLDbIdentifier loginRenamed1DLDbIdentifier = DLDbListCFPref::makeDLDbIdentifier(gGuidAppleCSPDL, version, 0, CSSM_SERVICE_CSP | CSSM_SERVICE_DL, loginRenamed1Keychain.c_str(), NULL);
1142
1143 //***************************************************************
1144 // make file renaming changes first
1145 //***************************************************************
1146
1147 // if "~/Library/Keychains/shortname" exists, we need to migrate it forward;
1148 // either to login.keychain if there isn't already one, otherwise to shortname.keychain
1149 if (shortnameKeychainExists) {
1150 int rename_stat = 0;
1151 if (loginKeychainExists) {
1152 struct stat st;
1153 int tmp_result = ::stat(loginDotKeychain.c_str(), &st);
1154 if (tmp_result == 0) {
1155 if (st.st_size <= kEmptyKeychainSizeInBytes) {
1156 tmp_result = ::unlink(loginDotKeychain.c_str());
1157 rename_stat = ::rename(shortnameKeychain.c_str(), loginDotKeychain.c_str());
1158 shortnameKeychainExists = (rename_stat != 0);
1159 }
1160 }
1161 }
1162 if (shortnameKeychainExists) {
1163 if (loginKeychainExists && !shortnameDotKeychainExists) {
1164 rename_stat = ::rename(shortnameKeychain.c_str(), shortnameDotKeychain.c_str());
1165 shortnameDotKeychainExists = (rename_stat == 0);
1166 } else if (!loginKeychainExists) {
1167 rename_stat = ::rename(shortnameKeychain.c_str(), loginDotKeychain.c_str());
1168 loginKeychainExists = (rename_stat == 0);
1169 } else {
1170 // we have all 3 keychains: login.keychain, shortname, and shortname.keychain.
1171 // on Leopard we never want a shortname keychain, so we must move it aside.
1172 char pathbuf[MAXPATHLEN];
1173 std::string shortnameRenamedXXXKeychain = keychainPath;
1174 shortnameRenamedXXXKeychain += userName;
1175 shortnameRenamedXXXKeychain += "_renamed_XXX.keychain";
1176 ::strlcpy(pathbuf, shortnameRenamedXXXKeychain.c_str(), sizeof(pathbuf));
1177 ::mkstemps(pathbuf, 9); // 9 == strlen(".keychain")
1178 rename_stat = ::rename(shortnameKeychain.c_str(), pathbuf);
1179 shortnameKeychainExists = (rename_stat != 0);
1180 }
1181 }
1182 if (rename_stat != 0) {
1183 MacOSError::throwMe(errno);
1184 }
1185 }
1186
1187 //***************************************************************
1188 // handle special case where user previously reset the keychain
1189 //***************************************************************
1190 // Since 9A581, we have changed the definition of kKeychainRenamedSuffix from "_renamed" to "_renamed_".
1191 // Therefore, if "login_renamed1.keychain" exists and there is no plist, the user may have run into a
1192 // prior upgrade issue and clicked Reset. If we can successfully unlock login_renamed1.keychain with the
1193 // supplied password, then we will attempt to rename it to login.keychain if that file is empty, or with
1194 // "shortname.keychain" if it is not.
1195
1196 if (loginRenamed1KeychainExists && (!loginKeychainExists ||
1197 (mSavedList.searchList().size() == 1 && mSavedList.member(loginDLDbIdentifier)) )) {
1198 try
1199 {
1200 Keychain loginRenamed1KC(keychain(loginRenamed1DLDbIdentifier));
1201 secdebug("KCLogin", "Attempting to unlock %s with %d-character password",
1202 (loginRenamed1KC) ? loginRenamed1KC->name() : "<NULL>", (unsigned int)passwordLength);
1203 loginRenamed1KC->unlock(CssmData(const_cast<void *>(password), passwordLength));
1204 // if we get here, we unlocked it
1205 if (loginKeychainExists) {
1206 struct stat st;
1207 int tmp_result = ::stat(loginDotKeychain.c_str(), &st);
1208 if (tmp_result == 0) {
1209 if (st.st_size <= kEmptyKeychainSizeInBytes) {
1210 tmp_result = ::unlink(loginDotKeychain.c_str());
1211 tmp_result = ::rename(loginRenamed1Keychain.c_str(), loginDotKeychain.c_str());
1212 } else if (!shortnameDotKeychainExists) {
1213 tmp_result = ::rename(loginRenamed1Keychain.c_str(), shortnameDotKeychain.c_str());
1214 shortnameDotKeychainExists = (tmp_result == 0);
1215 } else {
1216 throw 1; // can't do anything with it except move it out of the way
1217 }
1218 }
1219 } else {
1220 int tmp_result = ::rename(loginRenamed1Keychain.c_str(), loginDotKeychain.c_str());
1221 loginKeychainExists = (tmp_result == 0);
1222 }
1223 }
1224 catch(...)
1225 {
1226 // we failed to unlock the login_renamed1.keychain file with the login password.
1227 // move it aside so we don't try to deal with it again.
1228 char pathbuf[MAXPATHLEN];
1229 std::string loginRenamedXXXKeychain = keychainPath;
1230 loginRenamedXXXKeychain += "login_renamed_XXX.keychain";
1231 ::strlcpy(pathbuf, loginRenamedXXXKeychain.c_str(), sizeof(pathbuf));
1232 ::mkstemps(pathbuf, 9); // 9 == strlen(".keychain")
1233 ::rename(loginRenamed1Keychain.c_str(), pathbuf);
1234 }
1235 }
1236
1237 // if login.keychain does not exist at this point, create it
1238 if (!loginKeychainExists) {
1239 Keychain theKeychain(keychain(loginDLDbIdentifier));
1240 secdebug("KCLogin", "Creating login keychain %s", (loginDLDbIdentifier) ? loginDLDbIdentifier.dbName() : "<NULL>");
1241 theKeychain->create(passwordLength, password);
1242 secdebug("KCLogin", "Login keychain created successfully");
1243 loginKeychainExists = true;
1244 // Set the prefs for this new login keychain.
1245 loginKeychain(theKeychain);
1246 // Login Keychain does not lock on sleep nor lock after timeout by default.
1247 theKeychain->setSettings(INT_MAX, false);
1248 loginUnlocked = true;
1249 mSavedList.revert(true);
1250 }
1251
1252 //***************************************************************
1253 // make plist changes after files have been renamed or created
1254 //***************************************************************
1255
1256 // if the shortname keychain exists in the search list, either rename or remove the entry
1257 if (mSavedList.member(shortnameDLDbIdentifier)) {
1258 if (shortnameDotKeychainExists && !mSavedList.member(shortnameDotDLDbIdentifier)) {
1259 // change shortname to shortname.keychain (login.keychain will be added later if not present)
1260 secdebug("KCLogin", "Renaming %s to %s in keychain search list",
1261 (shortnameDLDbIdentifier) ? shortnameDLDbIdentifier.dbName() : "<NULL>",
1262 (shortnameDotDLDbIdentifier) ? shortnameDotDLDbIdentifier.dbName() : "<NULL>");
1263 mSavedList.rename(shortnameDLDbIdentifier, shortnameDotDLDbIdentifier);
1264 } else if (!mSavedList.member(loginDLDbIdentifier)) {
1265 // change shortname to login.keychain
1266 secdebug("KCLogin", "Renaming %s to %s in keychain search list",
1267 (shortnameDLDbIdentifier) ? shortnameDLDbIdentifier.dbName() : "<NULL>",
1268 (loginDLDbIdentifier) ? loginDLDbIdentifier.dbName() : "<NULL>");
1269 mSavedList.rename(shortnameDLDbIdentifier, loginDLDbIdentifier);
1270 } else {
1271 // already have login.keychain in list, and renaming to shortname.keychain isn't an option,
1272 // so just remove the entry
1273 secdebug("KCLogin", "Removing %s from keychain search list", (shortnameDLDbIdentifier) ? shortnameDLDbIdentifier.dbName() : "<NULL>");
1274 mSavedList.remove(shortnameDLDbIdentifier);
1275 }
1276
1277 // note: save() will cause the plist to be unlinked if the only remaining entry is for login.keychain
1278 mSavedList.save();
1279 mSavedList.revert(true);
1280 }
1281
1282 // make sure that login.keychain is in the search list
1283 if (!mSavedList.member(loginDLDbIdentifier)) {
1284 secdebug("KCLogin", "Adding %s to keychain search list", (loginDLDbIdentifier) ? loginDLDbIdentifier.dbName() : "<NULL>");
1285 mSavedList.add(loginDLDbIdentifier);
1286 mSavedList.save();
1287 mSavedList.revert(true);
1288 }
1289
1290 // if we have a shortname.keychain, always include it in the plist (after login.keychain)
1291 if (shortnameDotKeychainExists && !mSavedList.member(shortnameDotDLDbIdentifier)) {
1292 mSavedList.add(shortnameDotDLDbIdentifier);
1293 mSavedList.save();
1294 mSavedList.revert(true);
1295 }
1296
1297 // make sure that the default keychain is in the search list; if not, reset the default to login.keychain
1298 if (!mSavedList.member(mSavedList.defaultDLDbIdentifier())) {
1299 secdebug("KCLogin", "Changing default keychain to %s", (loginDLDbIdentifier) ? loginDLDbIdentifier.dbName() : "<NULL>");
1300 mSavedList.defaultDLDbIdentifier(loginDLDbIdentifier);
1301 mSavedList.save();
1302 mSavedList.revert(true);
1303 }
1304
1305 //***************************************************************
1306 // auto-unlock the login keychain(s)
1307 //***************************************************************
1308 // all our preflight fixups are finally done, so we can now attempt to unlock the login keychain
1309
1310 OSStatus loginResult = errSecSuccess;
1311 if (!loginUnlocked) {
1312 try
1313 {
1314 Keychain theKeychain(keychain(loginDLDbIdentifier));
1315 secdebug("KCLogin", "Attempting to unlock login keychain \"%s\" with %d-character password",
1316 (theKeychain) ? theKeychain->name() : "<NULL>", (unsigned int)passwordLength);
1317 theKeychain->unlock(CssmData(const_cast<void *>(password), passwordLength));
1318 loginUnlocked = true;
1319 }
1320 catch(const CssmError &e)
1321 {
1322 loginResult = e.osStatus(); // save this result
1323 }
1324 }
1325
1326 if (!loginUnlocked) {
1327 try {
1328 loginResult = errSecSuccess;
1329 Keychain theKeychain(keychain(loginDLDbIdentifier));
1330
1331 // build a fake key
1332 CssmKey key;
1333 key.header().BlobType = CSSM_KEYBLOB_RAW;
1334 key.header().Format = CSSM_KEYBLOB_RAW_FORMAT_OCTET_STRING;
1335 key.header().AlgorithmId = CSSM_ALGID_3DES_3KEY;
1336 key.header().KeyClass = CSSM_KEYCLASS_SESSION_KEY;
1337 key.header().KeyUsage = CSSM_KEYUSE_ENCRYPT | CSSM_KEYUSE_DECRYPT | CSSM_KEYATTR_EXTRACTABLE;
1338 key.header().KeyAttr = 0;
1339 key.KeyData = CssmData(const_cast<void *>(password), passwordLength);
1340
1341 // unwrap it into the CSP (but keep it raw)
1342 UnwrapKey unwrap(theKeychain->csp(), CSSM_ALGID_NONE);
1343 CssmKey masterKey;
1344 CssmData descriptiveData;
1345 unwrap(key,
1346 KeySpec(CSSM_KEYUSE_ANY, CSSM_KEYATTR_EXTRACTABLE),
1347 masterKey, &descriptiveData, NULL);
1348
1349 CssmClient::Db db = theKeychain->database();
1350
1351 // create the keychain, using appropriate credentials
1352 Allocator &alloc = db->allocator();
1353 AutoCredentials cred(alloc); // will leak, but we're quitting soon :-)
1354
1355 // use this passphrase
1356 cred += TypedList(alloc, CSSM_SAMPLE_TYPE_KEYCHAIN_LOCK,
1357 new(alloc) ListElement(CSSM_SAMPLE_TYPE_SYMMETRIC_KEY),
1358 new(alloc) ListElement(CssmData::wrap(theKeychain->csp()->handle())),
1359 new(alloc) ListElement(CssmData::wrap(masterKey)),
1360 new(alloc) ListElement(CssmData()));
1361 db->authenticate(CSSM_DB_ACCESS_READ, &cred);
1362 db->unlock();
1363 loginUnlocked = true;
1364 } catch (const CssmError &e) {
1365 loginResult = e.osStatus();
1366 }
1367 }
1368
1369 // if "shortname.keychain" exists and is in the search list, attempt to auto-unlock it with the same password
1370 if (shortnameDotKeychainExists && mSavedList.member(shortnameDotDLDbIdentifier)) {
1371 try
1372 {
1373 Keychain shortnameDotKC(keychain(shortnameDotDLDbIdentifier));
1374 secdebug("KCLogin", "Attempting to unlock %s",
1375 (shortnameDotKC) ? shortnameDotKC->name() : "<NULL>");
1376 shortnameDotKC->unlock(CssmData(const_cast<void *>(password), passwordLength));
1377 }
1378 catch(const CssmError &e)
1379 {
1380 // ignore; failure to unlock this keychain is not considered an error
1381 }
1382 }
1383
1384 if (loginResult != errSecSuccess) {
1385 MacOSError::throwMe(loginResult);
1386 }
1387 }
1388
1389 void StorageManager::stashLogin()
1390 {
1391 OSStatus loginResult = errSecSuccess;
1392
1393 DLDbIdentifier loginDLDbIdentifier;
1394 {
1395 mSavedList.revert(true);
1396 loginDLDbIdentifier = mSavedList.loginDLDbIdentifier();
1397 }
1398
1399 secdebug("KCLogin", "StorageManager::stash: loginDLDbIdentifier is %s", (loginDLDbIdentifier) ? loginDLDbIdentifier.dbName() : "<NULL>");
1400 if (!loginDLDbIdentifier)
1401 MacOSError::throwMe(errSecNoSuchKeychain);
1402
1403 try
1404 {
1405 CssmData empty;
1406 Keychain theKeychain(keychain(loginDLDbIdentifier));
1407 secdebug("KCLogin", "Attempting to use stash for login keychain \"%s\"",
1408 (theKeychain) ? theKeychain->name() : "<NULL>");
1409 theKeychain->stashCheck();
1410 }
1411 catch(const CssmError &e)
1412 {
1413 loginResult = e.osStatus(); // save this result
1414 }
1415
1416
1417 if (loginResult != errSecSuccess) {
1418 MacOSError::throwMe(loginResult);
1419 }
1420 }
1421
1422 void StorageManager::stashKeychain()
1423 {
1424 OSStatus loginResult = errSecSuccess;
1425
1426 DLDbIdentifier loginDLDbIdentifier;
1427 {
1428 mSavedList.revert(true);
1429 loginDLDbIdentifier = mSavedList.loginDLDbIdentifier();
1430 }
1431
1432 secdebug("KCLogin", "StorageManager::stash: loginDLDbIdentifier is %s", (loginDLDbIdentifier) ? loginDLDbIdentifier.dbName() : "<NULL>");
1433 if (!loginDLDbIdentifier)
1434 MacOSError::throwMe(errSecNoSuchKeychain);
1435
1436 try
1437 {
1438 Keychain theKeychain(keychain(loginDLDbIdentifier));
1439 secdebug("KCLogin", "Attempting to stash login keychain \"%s\"",
1440 (theKeychain) ? theKeychain->name() : "<NULL>");
1441 theKeychain->stash();
1442 }
1443 catch(const CssmError &e)
1444 {
1445 loginResult = e.osStatus(); // save this result
1446 }
1447
1448
1449 if (loginResult != errSecSuccess) {
1450 MacOSError::throwMe(loginResult);
1451 }
1452 }
1453
1454 void StorageManager::logout()
1455 {
1456 // nothing left to do here
1457 }
1458
1459 void StorageManager::changeLoginPassword(ConstStringPtr oldPassword, ConstStringPtr newPassword)
1460 {
1461 StLock<Mutex>_(mMutex);
1462
1463 loginKeychain()->changePassphrase(oldPassword, newPassword);
1464 secdebug("KClogin", "Changed login keychain password successfully");
1465 }
1466
1467
1468 void StorageManager::changeLoginPassword(UInt32 oldPasswordLength, const void *oldPassword, UInt32 newPasswordLength, const void *newPassword)
1469 {
1470 StLock<Mutex>_(mMutex);
1471
1472 loginKeychain()->changePassphrase(oldPasswordLength, oldPassword, newPasswordLength, newPassword);
1473 secdebug("KClogin", "Changed login keychain password successfully");
1474 }
1475
1476 // Clear out the keychain search list and rename the existing login.keychain.
1477 //
1478 void StorageManager::resetKeychain(Boolean resetSearchList)
1479 {
1480 StLock<Mutex>_(mMutex);
1481
1482 // Clear the keychain search list.
1483 try
1484 {
1485 if ( resetSearchList )
1486 {
1487 StorageManager::KeychainList keychainList;
1488 setSearchList(keychainList);
1489 }
1490 // Get a reference to the existing login keychain...
1491 // If we don't have one, we throw (not requiring a rename).
1492 //
1493 Keychain keychain = loginKeychain();
1494 //
1495 // Rename the existing login.keychain (i.e. put it aside).
1496 //
1497 CFMutableStringRef newName = NULL;
1498 newName = CFStringCreateMutable(NULL, 0);
1499 CFStringRef currName = NULL;
1500 currName = CFStringCreateWithCString(NULL, keychain->name(), kCFStringEncodingUTF8);
1501 if ( newName && currName )
1502 {
1503 CFStringAppend(newName, currName);
1504 CFStringRef kcSuffix = CFSTR(kKeychainSuffix);
1505 if ( CFStringHasSuffix(newName, kcSuffix) ) // remove the .keychain extension
1506 {
1507 CFRange suffixRange = CFStringFind(newName, kcSuffix, 0);
1508 CFStringFindAndReplace(newName, kcSuffix, CFSTR(""), suffixRange, 0);
1509 }
1510 CFStringAppend(newName, CFSTR(kKeychainRenamedSuffix)); // add "_renamed_"
1511 try
1512 {
1513 renameUnique(keychain, newName);
1514 }
1515 catch(...)
1516 {
1517 // we need to release 'newName' & 'currName'
1518 }
1519 } // else, let the login call report a duplicate
1520 if ( newName )
1521 CFRelease(newName);
1522 if ( currName )
1523 CFRelease(currName);
1524 }
1525 catch(...)
1526 {
1527 // We either don't have a login keychain, or there was a
1528 // failure to rename the existing one.
1529 }
1530 }
1531
1532 #pragma mark ____ File Related ____
1533
1534 Keychain StorageManager::make(const char *pathName)
1535 {
1536 return make(pathName, true);
1537 }
1538
1539 Keychain StorageManager::make(const char *pathName, bool add)
1540 {
1541 StLock<Mutex>_(mMutex);
1542
1543 string fullPathName;
1544 if ( pathName[0] == '/' )
1545 fullPathName = pathName;
1546 else
1547 {
1548 // Get Home directory from environment.
1549 switch (mDomain)
1550 {
1551 case kSecPreferencesDomainUser:
1552 {
1553 const char *homeDir = getenv("HOME");
1554 if (homeDir == NULL)
1555 {
1556 // If $HOME is unset get the current user's home directory
1557 // from the passwd file.
1558 uid_t uid = geteuid();
1559 if (!uid) uid = getuid();
1560 struct passwd *pw = getpwuid(uid);
1561 if (!pw)
1562 MacOSError::throwMe(errSecParam);
1563 homeDir = pw->pw_dir;
1564 }
1565 fullPathName = homeDir;
1566 }
1567 break;
1568 case kSecPreferencesDomainSystem:
1569 fullPathName = "";
1570 break;
1571 default:
1572 assert(false); // invalid domain for this
1573 }
1574
1575 fullPathName += "/Library/Keychains/";
1576 fullPathName += pathName;
1577 }
1578
1579 const CSSM_NET_ADDRESS *DbLocation = NULL; // NULL for keychains
1580 const CSSM_VERSION *version = NULL;
1581 uint32 subserviceId = 0;
1582 CSSM_SERVICE_TYPE subserviceType = CSSM_SERVICE_DL | CSSM_SERVICE_CSP;
1583 const CssmSubserviceUid ssuid(gGuidAppleCSPDL, version,
1584 subserviceId, subserviceType);
1585 DLDbIdentifier dLDbIdentifier(ssuid, fullPathName.c_str(), DbLocation);
1586 return makeKeychain(dLDbIdentifier, add);
1587 }
1588
1589 Keychain StorageManager::makeLoginAuthUI(const Item *item)
1590 {
1591 StLock<Mutex>_(mMutex);
1592
1593 // Create a login/default keychain for the user using UI.
1594 // The user can cancel out of the operation, or create a new login keychain.
1595 // If auto-login is turned off, the user will be asked for their login password.
1596 //
1597 OSStatus result = errSecSuccess;
1598 Keychain keychain; // We return this keychain.
1599 //
1600 // Set up the Auth ref to bring up UI.
1601 //
1602 AuthorizationItem *currItem, *authEnvirItemArrayPtr = NULL;
1603 AuthorizationRef authRef = NULL;
1604 try
1605 {
1606 result = AuthorizationCreate(NULL, NULL, kAuthorizationFlagDefaults, &authRef);
1607 if ( result )
1608 MacOSError::throwMe(result);
1609
1610 AuthorizationEnvironment envir;
1611 envir.count = 6; // up to 6 hints can be used.
1612 authEnvirItemArrayPtr = (AuthorizationItem*)malloc(sizeof(AuthorizationItem) * envir.count);
1613 if ( !authEnvirItemArrayPtr )
1614 MacOSError::throwMe(errAuthorizationInternal);
1615
1616 currItem = envir.items = authEnvirItemArrayPtr;
1617
1618 //
1619 // 1st Hint (optional): The keychain item's account attribute string.
1620 // When item is specified, we assume an 'add' operation is being attempted.
1621 char buff[256];
1622 UInt32 actLen = 0;
1623 SecKeychainAttribute attr = { kSecAccountItemAttr, 255, &buff };
1624 if ( item )
1625 {
1626 try
1627 {
1628 (*item)->getAttribute(attr, &actLen);
1629 }
1630 catch(...)
1631 {
1632 actLen = 0; // This item didn't have the account attribute, so don't display one in the UI.
1633 }
1634 }
1635 currItem->name = AGENT_HINT_ATTR_NAME; // name str that identifies this hint as attr name
1636 if ( actLen ) // Fill in the hint if we have an account attr
1637 {
1638 if ( actLen >= sizeof(buff) )
1639 buff[sizeof(buff)-1] = 0;
1640 else
1641 buff[actLen] = 0;
1642 currItem->valueLength = strlen(buff)+1;
1643 currItem->value = buff;
1644 }
1645 else
1646 {
1647 currItem->valueLength = 0;
1648 currItem->value = NULL;
1649 }
1650 currItem->flags = 0;
1651
1652 //
1653 // 2nd Hint (optional): The item's keychain full path.
1654 //
1655 currItem++;
1656 char* currDefaultName = NULL;
1657 try
1658 {
1659 currDefaultName = (char*)defaultKeychain()->name(); // Use the name if we have it.
1660 currItem->name = AGENT_HINT_LOGIN_KC_NAME; // Name str that identifies this hint as kc path
1661 currItem->valueLength = (currDefaultName) ? strlen(currDefaultName) : 0;
1662 currItem->value = (currDefaultName) ? (void*)currDefaultName : (void*)"";
1663 currItem->flags = 0;
1664 currItem++;
1665 }
1666 catch(...)
1667 {
1668 envir.count--;
1669 }
1670
1671 //
1672 // 3rd Hint (required): check if curr default keychain is unavailable.
1673 // This is determined by the parent not existing.
1674 //
1675 currItem->name = AGENT_HINT_LOGIN_KC_EXISTS_IN_KC_FOLDER;
1676 Boolean loginUnavail = false;
1677 try
1678 {
1679 Keychain defaultKC = defaultKeychain();
1680 if ( !defaultKC->exists() )
1681 loginUnavail = true;
1682 }
1683 catch(...) // login.keychain not present
1684 {
1685 }
1686 currItem->valueLength = sizeof(Boolean);
1687 currItem->value = (void*)&loginUnavail;
1688 currItem->flags = 0;
1689
1690 //
1691 // 4th Hint (required): userName
1692 //
1693 currItem++;
1694 currItem->name = AGENT_HINT_LOGIN_KC_USER_NAME;
1695 char* uName = getenv("USER");
1696 string userName = uName ? uName : "";
1697 if ( userName.length() == 0 )
1698 {
1699 uid_t uid = geteuid();
1700 if (!uid) uid = getuid();
1701 struct passwd *pw = getpwuid(uid); // fallback case...
1702 if (pw)
1703 userName = pw->pw_name;
1704 endpwent();
1705 }
1706 if ( userName.length() == 0 ) // did we ultimately get one?
1707 MacOSError::throwMe(errAuthorizationInternal);
1708
1709 currItem->value = (void*)userName.c_str();
1710 currItem->valueLength = userName.length();
1711 currItem->flags = 0;
1712
1713 //
1714 // 5th Hint (required): flags if user has more than 1 keychain (used for a later warning when reset to default).
1715 //
1716 currItem++;
1717 currItem->name = AGENT_HINT_LOGIN_KC_USER_HAS_OTHER_KCS_STR;
1718 Boolean moreThanOneKCExists = false;
1719 {
1720 // if item is NULL, then this is a user-initiated full reset
1721 if (item && mSavedList.searchList().size() > 1)
1722 moreThanOneKCExists = true;
1723 }
1724 currItem->value = &moreThanOneKCExists;
1725 currItem->valueLength = sizeof(Boolean);
1726 currItem->flags = 0;
1727
1728 //
1729 // 6th Hint (required): If no item is involved, this is a user-initiated full reset.
1730 // We want to suppress the "do you want to reset to defaults?" panel in this case.
1731 //
1732 currItem++;
1733 currItem->name = AGENT_HINT_LOGIN_KC_SUPPRESS_RESET_PANEL;
1734 Boolean suppressResetPanel = (item == NULL) ? TRUE : FALSE;
1735 currItem->valueLength = sizeof(Boolean);
1736 currItem->value = (void*)&suppressResetPanel;
1737 currItem->flags = 0;
1738
1739 //
1740 // Set up the auth rights and make the auth call.
1741 //
1742 AuthorizationItem authItem = { LOGIN_KC_CREATION_RIGHT, 0 , NULL, 0 };
1743 AuthorizationRights rights = { 1, &authItem };
1744 AuthorizationFlags flags = kAuthorizationFlagDefaults | kAuthorizationFlagInteractionAllowed | kAuthorizationFlagExtendRights;
1745 result = AuthorizationCopyRights(authRef, &rights, &envir, flags, NULL);
1746 if ( result )
1747 MacOSError::throwMe(result);
1748 try
1749 {
1750 resetKeychain(true); // Clears the plist, moves aside existing login.keychain
1751 }
1752 catch (...) // can throw if no existing login.keychain is found
1753 {
1754 }
1755 login(authRef, (UInt32)userName.length(), userName.c_str()); // Create login.keychain
1756 keychain = loginKeychain(); // Get newly-created login keychain
1757 defaultKeychain(keychain); // Set it to be the default
1758
1759 free(authEnvirItemArrayPtr);
1760 AuthorizationFree(authRef, kAuthorizationFlagDefaults);
1761 }
1762
1763 catch (...)
1764 {
1765 // clean up allocations, then rethrow error
1766 if ( authEnvirItemArrayPtr )
1767 free(authEnvirItemArrayPtr);
1768 if ( authRef )
1769 AuthorizationFree(authRef, kAuthorizationFlagDefaults);
1770 throw;
1771 }
1772
1773 return keychain;
1774 }
1775
1776 Keychain StorageManager::defaultKeychainUI(Item &item)
1777 {
1778 StLock<Mutex>_(mMutex);
1779
1780 Keychain returnedKeychain;
1781 try
1782 {
1783 returnedKeychain = defaultKeychain(); // If we have one, return it.
1784 if ( returnedKeychain->exists() )
1785 return returnedKeychain;
1786 }
1787 catch(...) // We could have one, but it isn't available (i.e. on a un-mounted volume).
1788 {
1789 }
1790 if ( globals().getUserInteractionAllowed() )
1791 {
1792 returnedKeychain = makeLoginAuthUI(&item); // If no Keychains is present, one will be created.
1793 if ( !returnedKeychain )
1794 MacOSError::throwMe(errSecInvalidKeychain); // Something went wrong...
1795 }
1796 else
1797 MacOSError::throwMe(errSecInteractionNotAllowed); // If UI isn't allowed, return an error.
1798
1799 return returnedKeychain;
1800 }
1801
1802 void
1803 StorageManager::addToDomainList(SecPreferencesDomain domain,
1804 const char* dbName, const CSSM_GUID &guid, uint32 subServiceType)
1805 {
1806 StLock<Mutex>_(mMutex);
1807
1808 if (domain == kSecPreferencesDomainDynamic)
1809 MacOSError::throwMe(errSecInvalidPrefsDomain);
1810
1811 // make the identifier
1812 CSSM_VERSION version = {0, 0};
1813 DLDbIdentifier id = DLDbListCFPref::makeDLDbIdentifier (guid, version, 0,
1814 subServiceType, dbName, NULL);
1815
1816 if (domain == mDomain)
1817 {
1818 // manipulate the user's list
1819 {
1820 mSavedList.revert(true);
1821 mSavedList.add(id);
1822 mSavedList.save();
1823 }
1824
1825 KCEventNotifier::PostKeychainEvent(kSecKeychainListChangedEvent);
1826 }
1827 else
1828 {
1829 // manipulate the other list
1830 DLDbListCFPref(domain).add(id);
1831 }
1832 }
1833
1834 void
1835 StorageManager::isInDomainList(SecPreferencesDomain domain,
1836 const char* dbName, const CSSM_GUID &guid, uint32 subServiceType)
1837 {
1838 StLock<Mutex>_(mMutex);
1839
1840 if (domain == kSecPreferencesDomainDynamic)
1841 MacOSError::throwMe(errSecInvalidPrefsDomain);
1842
1843 CSSM_VERSION version = {0, 0};
1844 DLDbIdentifier id = DLDbListCFPref::makeDLDbIdentifier (guid, version, 0,
1845 subServiceType, dbName, NULL);
1846
1847 // determine the list to search
1848 bool result;
1849 if (domain == mDomain)
1850 {
1851 result = mSavedList.member(id);
1852 }
1853 else
1854 {
1855 result = DLDbListCFPref(domain).member(id);
1856 }
1857
1858 // do the search
1859 if (!result)
1860 {
1861 MacOSError::throwMe(errSecNoSuchKeychain);
1862 }
1863 }
1864
1865 void
1866 StorageManager::removeFromDomainList(SecPreferencesDomain domain,
1867 const char* dbName, const CSSM_GUID &guid, uint32 subServiceType)
1868 {
1869 StLock<Mutex>_(mMutex);
1870
1871 if (domain == kSecPreferencesDomainDynamic)
1872 MacOSError::throwMe(errSecInvalidPrefsDomain);
1873
1874 // make the identifier
1875 CSSM_VERSION version = {0, 0};
1876 DLDbIdentifier id = DLDbListCFPref::makeDLDbIdentifier (guid, version, 0,
1877 subServiceType, dbName, NULL);
1878
1879 if (domain == mDomain)
1880 {
1881 // manipulate the user's list
1882 {
1883 mSavedList.revert(true);
1884 mSavedList.remove(id);
1885 mSavedList.save();
1886 }
1887
1888 KCEventNotifier::PostKeychainEvent(kSecKeychainListChangedEvent);
1889 }
1890 else
1891 {
1892 // manipulate the other list
1893 DLDbListCFPref(domain).remove(id);
1894 }
1895 }
1896
1897 bool
1898 StorageManager::keychainOwnerPermissionsValidForDomain(const char* path, SecPreferencesDomain domain)
1899 {
1900 struct stat sb;
1901 mode_t perms;
1902 const char* sysPrefDir = "/Library/Preferences";
1903 const char* errMsg = "Will not set default";
1904 char* mustOwnDir = NULL;
1905 struct passwd* pw = NULL;
1906
1907 // get my uid
1908 uid_t uid = geteuid();
1909 if (!uid) uid = getuid();
1910
1911 // our (e)uid must own the appropriate preferences or home directory
1912 // for the specified preference domain whose default we will be modifying
1913 switch (domain) {
1914 case kSecPreferencesDomainUser:
1915 mustOwnDir = getenv("HOME");
1916 if (mustOwnDir == NULL) {
1917 pw = getpwuid(uid);
1918 if (!pw) return false;
1919 mustOwnDir = pw->pw_dir;
1920 }
1921 break;
1922 case kSecPreferencesDomainSystem:
1923 mustOwnDir = (char*)sysPrefDir;
1924 break;
1925 case kSecPreferencesDomainCommon:
1926 mustOwnDir = (char*)sysPrefDir;
1927 break;
1928 default:
1929 return false;
1930 }
1931
1932 if (mustOwnDir != NULL) {
1933 struct stat dsb;
1934 if ( (stat(mustOwnDir, &dsb) != 0) || (dsb.st_uid != uid) ) {
1935 fprintf(stderr, "%s: UID=%d does not own directory %s\n", errMsg, (int)uid, mustOwnDir);
1936 mustOwnDir = NULL; // will return below after calling endpwent()
1937 }
1938 }
1939
1940 if (pw != NULL)
1941 endpwent();
1942
1943 if (mustOwnDir == NULL)
1944 return false;
1945
1946 // check that file actually exists
1947 if (stat(path, &sb) != 0) {
1948 fprintf(stderr, "%s: file %s does not exist\n", errMsg, path);
1949 return false;
1950 }
1951
1952 // check flags
1953 if (sb.st_flags & (SF_IMMUTABLE | UF_IMMUTABLE)) {
1954 fprintf(stderr, "%s: file %s is immutable\n", errMsg, path);
1955 return false;
1956 }
1957
1958 // check ownership
1959 if (sb.st_uid != uid) {
1960 fprintf(stderr, "%s: file %s is owned by UID=%d, but we have UID=%d\n",
1961 errMsg, path, (int)sb.st_uid, (int)uid);
1962 return false;
1963 }
1964
1965 // check mode
1966 perms = sb.st_mode;
1967 perms |= 0600; // must have owner read/write permission set
1968 if (sb.st_mode != perms) {
1969 fprintf(stderr, "%s: file %s does not have the expected permissions\n", errMsg, path);
1970 return false;
1971 }
1972
1973 // user owns file and can read/write it
1974 return true;
1975 }