]> git.saurik.com Git - apple/security.git/blob - OSX/libsecurity_keychain/lib/StorageManager.cpp
Security-57740.20.22.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_utilities/debugging.h>
44 #include <security_keychain/SecCFTypes.h>
45 #include <securityd_client/ssclient.h>
46 #include <Security/AuthorizationTags.h>
47 #include <Security/AuthorizationTagsPriv.h>
48 #include <Security/SecTask.h>
49 #include <security_keychain/SecCFTypes.h>
50 #include "TrustSettingsSchema.h"
51 #include <security_cdsa_client/wrapkey.h>
52 #include <securityd_client/ssblob.h>
53 #include <SecBasePriv.h>
54 #include "TokenLogin.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 secnotice("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 secnotice("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 DLDbIdentifier dldbi = mungeDLDbIdentifier(dLDbIdentifier, false);
157
158 KeychainMap::iterator it = mKeychainMap.find(dldbi);
159 if (it != mKeychainMap.end())
160 {
161 return it->second;
162 }
163
164 // If we have a keychain object for the un/demunged keychain, return that.
165 // We might be in the middle of an upgrade...
166 DLDbIdentifier demunge_dldbi = demungeDLDbIdentifier(dLDbIdentifier);
167 it = mKeychainMap.find(demunge_dldbi);
168 if (it != mKeychainMap.end()) {
169 secnotice("integrity", "returning unmunged keychain ref");
170 return it->second;
171 }
172
173 if (gServerMode) {
174 secnotice("servermode", "keychain reference in server mode");
175 return Keychain();
176 }
177
178 // The keychain is not in our cache. Create it.
179 Db db(makeDb(dldbi));
180
181 Keychain keychain(db);
182 // Add the keychain to the cache.
183 registerKeychain(keychain);
184
185 return keychain;
186 }
187
188 // Note: this must be a munged DLDbidentifier.
189 CssmClient::Db
190 StorageManager::makeDb(DLDbIdentifier dLDbIdentifier) {
191 Module module(dLDbIdentifier.ssuid().guid());
192
193 DL dl;
194 if (dLDbIdentifier.ssuid().subserviceType() & CSSM_SERVICE_CSP)
195 dl = SSCSPDL(module);
196 else
197 dl = DL(module);
198
199 dl->subserviceId(dLDbIdentifier.ssuid().subserviceId());
200 dl->version(dLDbIdentifier.ssuid().version());
201
202 CssmClient::Db db(dl, dLDbIdentifier.dbName());
203
204 return db;
205 }
206
207 // StorageManager is responsible for silently switching to newer-style keychains.
208 // If the keychain requested is in ~/Library/Keychains/, and there is a
209 // newer keychain available (with extension ".keychain-db"), open that one
210 // instead of the one requested.
211 //
212 // Because of backwards compatibility reasons, we can't update the plist
213 // files on disk to point to the upgraded keychains. We will be asked to
214 // load "/Users/account/Library/Keychains/login.keychain", hence this
215 // modification to 'login.keychain-db'.
216 DLDbIdentifier
217 StorageManager::mungeDLDbIdentifier(const DLDbIdentifier& dLDbIdentifier, bool isReset) {
218 if(!dLDbIdentifier.dbName()) {
219 // If this DLDbIdentifier doesn't have a filename, don't munge it
220 return dLDbIdentifier;
221 }
222
223 string path = dLDbIdentifier.dbName();
224
225 bool shouldCreateProtected = globals().integrityProtection();
226
227 // If we don't have a DLDbIdentifier, we can't return one
228 if(dLDbIdentifier.mImpl == NULL) {
229 return DLDbIdentifier();
230 }
231
232 // Ensure we're in ~/Library/Keychains
233 if(pathInHomeLibraryKeychains(path)) {
234 string pathdb = makeKeychainDbFilename(path);
235
236 struct stat st;
237 int stat_result;
238 stat_result = ::stat(path.c_str(), &st);
239 bool path_exists = (stat_result == 0);
240
241 stat_result = ::stat(pathdb.c_str(), &st);
242 bool pathdb_exists = (stat_result == 0);
243
244 // If protections are off, don't change the requested filename.
245 // If protictions are on and the -db file exists, always use it.
246 //
247 // If we're resetting, and we're creating a new-style keychain, use the -db path.
248 // If we're resetting, and we're creating an old-style keychain, use the original path.
249 //
250 // Protection pathdb_exists path_exists resetting Result
251 // DISABLED X X X original
252 // ENABLED 1 X X -db
253 // ENABLED 0 0 X -db
254 // ENABLED 0 1 0 original
255 // ENABLED 0 1 1 -db
256 //
257 bool switchPaths = shouldCreateProtected && (pathdb_exists || (!pathdb_exists && !path_exists) || isReset);
258
259 if(switchPaths) {
260 secnotice("integrity", "switching to keychain-db: %s from %s (%d %d %d %d)", pathdb.c_str(), path.c_str(), isReset, shouldCreateProtected, path_exists, pathdb_exists);
261 path = pathdb;
262 } else {
263 secnotice("integrity", "not switching: %s from %s (%d %d %d %d)", pathdb.c_str(), path.c_str(), isReset, shouldCreateProtected, path_exists, pathdb_exists);
264 }
265 } else {
266 secnotice("integrity", "not switching as we're not in ~/Library/Keychains/: %s (%d)", path.c_str(), isReset);
267 }
268
269 DLDbIdentifier id(dLDbIdentifier.ssuid(), path.c_str(), dLDbIdentifier.dbLocation());
270 return id;
271 }
272
273 DLDbIdentifier
274 StorageManager::demungeDLDbIdentifier(const DLDbIdentifier& dLDbIdentifier) {
275 if(dLDbIdentifier.dbName() == NULL) {
276 return dLDbIdentifier;
277 }
278
279 string path = dLDbIdentifier.dbName();
280 string dbSuffix = "-db";
281 bool endsWithKeychainDb = (path.size() > dbSuffix.size() && (0 == path.compare(path.size() - dbSuffix.size(), dbSuffix.size(), dbSuffix)));
282
283 // Ensure we're in ~/Library/Keychains, and that the path ends in "-db"
284 if(pathInHomeLibraryKeychains(path) && endsWithKeychainDb) {
285 // remove "-db" from the end.
286 path.erase(path.end() - 3, path.end());
287 }
288
289 DLDbIdentifier id(dLDbIdentifier.ssuid(), path.c_str(), dLDbIdentifier.dbLocation());
290 return id;
291 }
292
293 string
294 StorageManager::makeKeychainDbFilename(const string& filename) {
295 string keychainDbSuffix = "-db";
296 bool endsWithKeychainDb = (filename.size() > keychainDbSuffix.size() && (0 == filename.compare(filename.size() - keychainDbSuffix.size(), keychainDbSuffix.size(), keychainDbSuffix)));
297
298 if(endsWithKeychainDb) {
299 return filename;
300 } else {
301 return filename + keychainDbSuffix;
302 }
303 }
304
305 bool
306 StorageManager::pathInHomeLibraryKeychains(const string& path) {
307 return SecurityServer::CommonBlob::pathInHomeLibraryKeychains(path);
308 }
309
310 void
311 StorageManager::reloadKeychain(Keychain keychain) {
312 StLock<Mutex>_(mKeychainMapMutex);
313
314 DLDbIdentifier dLDbIdentifier = keychain->database()->dlDbIdentifier();
315
316 keychain->changeDatabase(makeDb(mungeDLDbIdentifier(dLDbIdentifier, false)));
317
318 // This keychain might have a different dldbidentifier now, depending on what
319 // other processes have been doing to the keychain files. Let's re-register it, just
320 // to be sure.
321 registerKeychain(keychain);
322 }
323
324 void
325 StorageManager::removeKeychain(const DLDbIdentifier &dLDbIdentifier,
326 KeychainImpl *keychainImpl)
327 {
328 StLock<Mutex>_(mKeychainMapMutex);
329
330 // Don't trust this dldbidentifier. Just look for the keychain and delete it.
331 forceRemoveFromCache(keychainImpl);
332 }
333
334 void
335 StorageManager::didRemoveKeychain(const DLDbIdentifier &dLDbIdentifier)
336 {
337 // Lock the recursive mutex
338
339 StLock<Mutex>_(mKeychainMapMutex);
340
341 KeychainMap::iterator it = mKeychainMap.find(dLDbIdentifier);
342 if (it != mKeychainMap.end())
343 {
344 it->second->inCache(false);
345 mKeychainMap.erase(it);
346 }
347 }
348
349 // If the client does not keep references to keychains, they are destroyed on
350 // every API exit, and recreated on every API entrance.
351 //
352 // To improve performance, we'll cache keychains for some short period of time.
353 // We'll do this by CFRetaining the keychain object, and setting a timer to
354 // CFRelease it when time's up. This way, the client can still recover all its
355 // memory if it doesn't want the keychains around, but repeated API calls will
356 // be significantly faster.
357 //
358 void
359 StorageManager::tickleKeychain(KeychainImpl *keychainImpl) {
360 static dispatch_once_t onceToken = 0;
361 static dispatch_queue_t release_queue = NULL;
362 dispatch_once(&onceToken, ^{
363 release_queue = dispatch_queue_create("com.apple.security.keychain-cache-queue", DISPATCH_QUEUE_SERIAL);
364 });
365
366 __block KeychainImpl* kcImpl = keychainImpl;
367
368 if(!kcImpl) {
369 return;
370 }
371
372 // We really only want to cache CSPDL file-based keychains
373 if(kcImpl->dlDbIdentifier().ssuid().guid() != gGuidAppleCSPDL) {
374 return;
375 }
376
377 // Make a one-shot timer to release the keychain
378 uint32_t seconds = 1;
379
380 const string path = kcImpl->name();
381 bool isSystemKeychain = (0 == path.compare("/Library/Keychains/System.keychain"));
382 if(pathInHomeLibraryKeychains(path) || isSystemKeychain) {
383 // These keychains are important and likely aren't on removable media.
384 // Cache them longer.
385 seconds = 5;
386 }
387
388 __block CFTypeRef kcHandle = kcImpl->handle(); // calls retain; this keychain object will stay around until our dispatch block fires.
389
390 dispatch_async(release_queue, ^() {
391 if(kcImpl->mCacheTimer) {
392 // Update the cache timer to be seconds from now
393 dispatch_source_set_timer(kcImpl->mCacheTimer, dispatch_time(DISPATCH_TIME_NOW, seconds * NSEC_PER_SEC), DISPATCH_TIME_FOREVER, NSEC_PER_SEC/2);
394
395 // We've added an extra retain to this keychain right before invoking this block. Release it.
396 CFRelease(kcHandle);
397
398 } else {
399 // No cache timer; make one.
400 kcImpl->mCacheTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, release_queue);
401 dispatch_source_set_timer(kcImpl->mCacheTimer, dispatch_time(DISPATCH_TIME_NOW, seconds * NSEC_PER_SEC), DISPATCH_TIME_FOREVER, NSEC_PER_SEC/2);
402
403 dispatch_source_set_event_handler(kcImpl->mCacheTimer, ^{
404 dispatch_source_cancel(kcImpl->mCacheTimer);
405 dispatch_release(kcImpl->mCacheTimer);
406 kcImpl->mCacheTimer = NULL;
407 CFRelease(kcHandle);
408 });
409
410 dispatch_resume(kcImpl->mCacheTimer);
411 }
412 });
413 }
414
415 // Create keychain if it doesn't exist, and optionally add it to the search list.
416 Keychain
417 StorageManager::makeKeychain(const DLDbIdentifier &dLDbIdentifier, bool add, bool isReset)
418 {
419 StLock<Mutex>_(mKeychainMapMutex);
420
421 Keychain theKeychain = keychain(mungeDLDbIdentifier(dLDbIdentifier, isReset));
422 bool post = false;
423 bool updateList = (add && shouldAddToSearchList(dLDbIdentifier));
424
425 if (updateList)
426 {
427 mSavedList.revert(false);
428 DLDbList searchList = mSavedList.searchList();
429 if (find(searchList.begin(), searchList.end(), demungeDLDbIdentifier(dLDbIdentifier)) != searchList.end())
430 return theKeychain; // theKeychain is already in the searchList.
431
432 mCommonList.revert(false);
433 searchList = mCommonList.searchList();
434 if (find(searchList.begin(), searchList.end(), demungeDLDbIdentifier(dLDbIdentifier)) != searchList.end())
435 return theKeychain; // theKeychain is already in the commonList don't add it to the searchList.
436
437 // If theKeychain doesn't exist don't bother adding it to the search list yet.
438 if (!theKeychain->exists())
439 return theKeychain;
440
441 // theKeychain exists and is not in our search list, so add it to the
442 // search list.
443 mSavedList.revert(true);
444 mSavedList.add(demungeDLDbIdentifier(dLDbIdentifier));
445 mSavedList.save();
446 post = true;
447 }
448
449 if (post)
450 {
451 // Make sure we are not holding mStorageManagerLock anymore when we
452 // post this event.
453 KCEventNotifier::PostKeychainEvent(kSecKeychainListChangedEvent);
454 }
455
456 return theKeychain;
457 }
458
459 // Be notified a Keychain just got created.
460 void
461 StorageManager::created(const Keychain &keychain)
462 {
463 StLock<Mutex>_(mKeychainMapMutex);
464
465 DLDbIdentifier dLDbIdentifier = keychain->dlDbIdentifier();
466 bool defaultChanged = false;
467 bool updateList = shouldAddToSearchList(dLDbIdentifier);
468
469 if (updateList)
470 {
471 mSavedList.revert(true);
472 // If we don't have a default Keychain yet. Make the newly created
473 // keychain the default.
474 if (!mSavedList.defaultDLDbIdentifier())
475 {
476 mSavedList.defaultDLDbIdentifier(demungeDLDbIdentifier(dLDbIdentifier));
477 defaultChanged = true;
478 }
479
480 // Add the keychain to the search list prefs.
481 mSavedList.add(demungeDLDbIdentifier(dLDbIdentifier));
482 mSavedList.save();
483
484 // Make sure we are not holding mLock when we post these events.
485 KCEventNotifier::PostKeychainEvent(kSecKeychainListChangedEvent);
486 }
487
488 if (defaultChanged)
489 {
490 KCEventNotifier::PostKeychainEvent(kSecDefaultChangedEvent, dLDbIdentifier);
491 }
492 }
493
494 KCCursor
495 StorageManager::createCursor(SecItemClass itemClass,
496 const SecKeychainAttributeList *attrList)
497 {
498 StLock<Mutex>_(mMutex);
499
500 KeychainList searchList;
501 getSearchList(searchList);
502 return KCCursor(searchList, itemClass, attrList);
503 }
504
505 KCCursor
506 StorageManager::createCursor(const SecKeychainAttributeList *attrList)
507 {
508 StLock<Mutex>_(mMutex);
509
510 KeychainList searchList;
511 getSearchList(searchList);
512 return KCCursor(searchList, attrList);
513 }
514
515 void
516 StorageManager::lockAll()
517 {
518 StLock<Mutex>_(mMutex);
519
520 SecurityServer::ClientSession ss(Allocator::standard(), Allocator::standard());
521 ss.lockAll (false);
522 }
523
524 Keychain
525 StorageManager::defaultKeychain()
526 {
527 StLock<Mutex>_(mMutex);
528
529 Keychain theKeychain;
530 CFTypeRef ref;
531
532 {
533 mSavedList.revert(false);
534 DLDbIdentifier defaultDLDbIdentifier(mSavedList.defaultDLDbIdentifier());
535 if (defaultDLDbIdentifier)
536 {
537 theKeychain = keychain(defaultDLDbIdentifier);
538 ref = theKeychain->handle(false);
539 }
540 }
541
542 if (theKeychain /* && theKeychain->exists() */)
543 return theKeychain;
544
545 MacOSError::throwMe(errSecNoDefaultKeychain);
546 }
547
548 void
549 StorageManager::defaultKeychain(const Keychain &keychain)
550 {
551 StLock<Mutex>_(mMutex);
552
553 // Only set a keychain as the default if we own it and can read/write it,
554 // and our uid allows modifying the directory for that preference domain.
555 if (!keychainOwnerPermissionsValidForDomain(keychain->name(), mDomain))
556 MacOSError::throwMe(errSecWrPerm);
557
558 DLDbIdentifier oldDefaultId;
559 DLDbIdentifier newDefaultId(keychain->dlDbIdentifier());
560 {
561 oldDefaultId = mSavedList.defaultDLDbIdentifier();
562 mSavedList.revert(true);
563 mSavedList.defaultDLDbIdentifier(demungeDLDbIdentifier(newDefaultId));
564 mSavedList.save();
565 }
566
567 if (!(oldDefaultId == newDefaultId))
568 {
569 // Make sure we are not holding mLock when we post this event.
570 KCEventNotifier::PostKeychainEvent(kSecDefaultChangedEvent, newDefaultId);
571 }
572 }
573
574 Keychain
575 StorageManager::defaultKeychain(SecPreferencesDomain domain)
576 {
577 StLock<Mutex>_(mMutex);
578
579 if (domain == kSecPreferencesDomainDynamic)
580 MacOSError::throwMe(errSecInvalidPrefsDomain);
581
582 if (domain == mDomain)
583 return defaultKeychain();
584 else
585 {
586 DLDbIdentifier defaultDLDbIdentifier(DLDbListCFPref(domain).defaultDLDbIdentifier());
587 if (defaultDLDbIdentifier)
588 return keychain(defaultDLDbIdentifier);
589
590 MacOSError::throwMe(errSecNoDefaultKeychain);
591 }
592 }
593
594 void
595 StorageManager::defaultKeychain(SecPreferencesDomain domain, const Keychain &keychain)
596 {
597 StLock<Mutex>_(mMutex);
598
599 if (domain == kSecPreferencesDomainDynamic)
600 MacOSError::throwMe(errSecInvalidPrefsDomain);
601
602 if (domain == mDomain)
603 defaultKeychain(keychain);
604 else
605 DLDbListCFPref(domain).defaultDLDbIdentifier(keychain->dlDbIdentifier());
606 }
607
608 Keychain
609 StorageManager::loginKeychain()
610 {
611 StLock<Mutex>_(mMutex);
612
613 Keychain theKeychain;
614 {
615 mSavedList.revert(false);
616 DLDbIdentifier loginDLDbIdentifier(mSavedList.loginDLDbIdentifier());
617 if (loginDLDbIdentifier)
618 {
619 theKeychain = keychain(loginDLDbIdentifier);
620 }
621 }
622
623 if (theKeychain && theKeychain->exists())
624 return theKeychain;
625
626 MacOSError::throwMe(errSecNoSuchKeychain);
627 }
628
629 void
630 StorageManager::loginKeychain(Keychain keychain)
631 {
632 StLock<Mutex>_(mMutex);
633
634 mSavedList.revert(true);
635 mSavedList.loginDLDbIdentifier(demungeDLDbIdentifier(keychain->dlDbIdentifier()));
636 mSavedList.save();
637 }
638
639 size_t
640 StorageManager::size()
641 {
642 StLock<Mutex>_(mMutex);
643
644 mSavedList.revert(false);
645 mCommonList.revert(false);
646 return mSavedList.searchList().size() + mCommonList.searchList().size();
647 }
648
649 Keychain
650 StorageManager::at(unsigned int ix)
651 {
652 StLock<Mutex>_(mMutex);
653
654 mSavedList.revert(false);
655 DLDbList dLDbList = mSavedList.searchList();
656 if (ix < dLDbList.size())
657 {
658 return keychain(dLDbList[ix]);
659 }
660 else
661 {
662 ix -= dLDbList.size();
663 mCommonList.revert(false);
664 DLDbList commonList = mCommonList.searchList();
665 if (ix >= commonList.size())
666 MacOSError::throwMe(errSecInvalidKeychain);
667
668 return keychain(commonList[ix]);
669 }
670 }
671
672 Keychain
673 StorageManager::operator[](unsigned int ix)
674 {
675 StLock<Mutex>_(mMutex);
676
677 return at(ix);
678 }
679
680 void StorageManager::rename(Keychain keychain, const char* newName)
681 {
682
683 StLock<Mutex>_(mKeychainMapMutex);
684
685 bool changedDefault = false;
686 DLDbIdentifier newDLDbIdentifier;
687 {
688 mSavedList.revert(true);
689 DLDbIdentifier defaultId = mSavedList.defaultDLDbIdentifier();
690
691 // Find the keychain object for the given ref
692 DLDbIdentifier dLDbIdentifier = keychain->dlDbIdentifier();
693
694 if(!keychain->database()->isLocked()) {
695 // Bring our unlock state with us
696 DLDbIdentifier dldbi(dLDbIdentifier.ssuid(), newName, dLDbIdentifier.dbLocation());
697 keychain->database()->transferTo(dldbi);
698 } else {
699 keychain->database()->rename(newName);
700 }
701
702 if (demungeDLDbIdentifier(dLDbIdentifier) == defaultId)
703 changedDefault=true;
704
705 newDLDbIdentifier = keychain->dlDbIdentifier();
706 // Rename the keychain in the search list.
707 mSavedList.rename(demungeDLDbIdentifier(dLDbIdentifier), demungeDLDbIdentifier(newDLDbIdentifier));
708
709 // If this was the default keychain change it accordingly
710 if (changedDefault)
711 mSavedList.defaultDLDbIdentifier(demungeDLDbIdentifier(newDLDbIdentifier));
712
713 mSavedList.save();
714
715 // If the keychain wasn't in the cache, don't touch the cache.
716 // Otherwise, update the cache to use its current identifier.
717 if(keychain->inCache()) {
718 registerKeychain(keychain);
719 }
720 }
721
722 KCEventNotifier::PostKeychainEvent(kSecKeychainListChangedEvent);
723
724 if (changedDefault)
725 KCEventNotifier::PostKeychainEvent(kSecDefaultChangedEvent,
726 newDLDbIdentifier);
727 }
728
729 void StorageManager::registerKeychain(Keychain& kc) {
730 registerKeychainImpl(kc.get());
731 }
732
733 void StorageManager::registerKeychainImpl(KeychainImpl* kcimpl) {
734 if(!kcimpl) {
735 return;
736 }
737
738 {
739 StLock<Mutex> _(mKeychainMapMutex);
740
741 // First, iterate through the cache to see if this keychain is there. If so, remove it.
742 forceRemoveFromCache(kcimpl);
743
744 // If we renamed this keychain on top of an existing one, let's drop the old one from the cache.
745 KeychainMap::iterator it = mKeychainMap.find(kcimpl->dlDbIdentifier());
746 if (it != mKeychainMap.end())
747 {
748 Keychain oldKeychain(it->second);
749 oldKeychain->inCache(false);
750 // @@@ Ideally we should invalidate or fault this keychain object.
751 }
752
753 mKeychainMap.insert(KeychainMap::value_type(kcimpl->dlDbIdentifier(), kcimpl));
754 kcimpl->inCache(true);
755 } // drop mKeychainMapMutex
756 }
757
758 void StorageManager::forceRemoveFromCache(KeychainImpl* inKeychainImpl) {
759 try {
760 // Wrap all this in a try-block and ignore all errors - we're trying to clean up these maps
761 {
762 StLock<Mutex> _(mKeychainMapMutex);
763 for(KeychainMap::iterator it = mKeychainMap.begin(); it != mKeychainMap.end(); ) {
764 if(it->second == inKeychainImpl) {
765 // Increment the iterator, but use its pre-increment value for the erase
766 it->second->inCache(false);
767 mKeychainMap.erase(it++);
768 } else {
769 it++;
770 }
771 }
772 } // drop mKeychainMapMutex
773 } catch(UnixError ue) {
774 secnotice("storagemgr", "caught UnixError: %d %s", ue.unixError(), ue.what());
775 } catch (CssmError cssme) {
776 const char* errStr = cssmErrorString(cssme.error);
777 secnotice("storagemgr", "caught CssmError: %d %s", (int) cssme.error, errStr);
778 } catch (MacOSError mose) {
779 secnotice("storagemgr", "MacOSError: %d", (int)mose.osStatus());
780 } catch(...) {
781 secnotice("storagemgr", "Unknown error");
782 }
783 }
784
785 void StorageManager::renameUnique(Keychain keychain, CFStringRef newName, bool appendDbSuffix)
786 {
787 StLock<Mutex>_(mMutex);
788
789 bool doneCreating = false;
790 int index = 1;
791 do
792 {
793 char newNameCString[MAXPATHLEN];
794 if ( CFStringGetCString(newName, newNameCString, MAXPATHLEN, kCFStringEncodingUTF8) ) // make sure it fits in MAXPATHLEN, etc.
795 {
796 // Construct the new name...
797 //
798 CFMutableStringRef newNameCFStr = NULL;
799 newNameCFStr = CFStringCreateMutable(NULL, MAXPATHLEN);
800 if ( newNameCFStr )
801 {
802 CFStringAppendFormat(newNameCFStr, NULL, CFSTR("%s%d"), newNameCString, index);
803 if(appendDbSuffix) {
804 CFStringAppend(newNameCFStr, CFSTR(kKeychainDbSuffix));
805 } else {
806 CFStringAppend(newNameCFStr, CFSTR(kKeychainSuffix)); // add .keychain
807 }
808 char toUseBuff2[MAXPATHLEN];
809 if ( CFStringGetCString(newNameCFStr, toUseBuff2, MAXPATHLEN, kCFStringEncodingUTF8) ) // make sure it fits in MAXPATHLEN, etc.
810 {
811 struct stat filebuf;
812 if ( lstat(toUseBuff2, &filebuf) )
813 {
814 rename(keychain, toUseBuff2);
815 KeychainList kcList;
816 kcList.push_back(keychain);
817 remove(kcList, false);
818 doneCreating = true;
819 }
820 else
821 index++;
822 }
823 else
824 doneCreating = true; // failure to get c string.
825 CFRelease(newNameCFStr);
826 }
827 else
828 doneCreating = false; // failure to create mutable string.
829 }
830 else
831 doneCreating = false; // failure to get the string (i.e. > MAXPATHLEN?)
832 }
833 while (!doneCreating && index != INT_MAX);
834 }
835
836 #define KEYCHAIN_SYNC_KEY CFSTR("KeychainSyncList")
837 #define KEYCHAIN_SYNC_DOMAIN CFSTR("com.apple.keychainsync")
838
839 static CFStringRef MakeExpandedPath (const char* path)
840 {
841 std::string name = DLDbListCFPref::ExpandTildesInPath (std::string (path));
842 CFStringRef expanded = CFStringCreateWithCString (NULL, name.c_str (), 0);
843 return expanded;
844 }
845
846 void StorageManager::removeKeychainFromSyncList (const DLDbIdentifier &id)
847 {
848 StLock<Mutex>_(mMutex);
849
850 // make a CFString of our identifier
851 const char* idname = id.dbName ();
852 if (idname == NULL)
853 {
854 return;
855 }
856
857 CFRef<CFStringRef> idString = MakeExpandedPath (idname);
858
859 // check and see if this keychain is in the keychain syncing list
860 CFArrayRef value =
861 (CFArrayRef) CFPreferencesCopyValue (KEYCHAIN_SYNC_KEY,
862 KEYCHAIN_SYNC_DOMAIN,
863 kCFPreferencesCurrentUser,
864 kCFPreferencesAnyHost);
865 if (value == NULL)
866 {
867 return;
868 }
869
870 // make a mutable copy of the dictionary
871 CFRef<CFMutableArrayRef> mtValue = CFArrayCreateMutableCopy (NULL, 0, value);
872 CFRelease (value);
873
874 // walk the array, looking for the value
875 CFIndex i;
876 CFIndex limit = CFArrayGetCount (mtValue.get());
877 bool found = false;
878
879 for (i = 0; i < limit; ++i)
880 {
881 CFDictionaryRef idx = (CFDictionaryRef) CFArrayGetValueAtIndex (mtValue.get(), i);
882 CFStringRef v = (CFStringRef) CFDictionaryGetValue (idx, CFSTR("DbName"));
883 if (v == NULL)
884 {
885 return; // something is really wrong if this is taken
886 }
887
888 char* stringBuffer = NULL;
889 const char* pathString = CFStringGetCStringPtr(v, 0);
890 if (pathString == 0)
891 {
892 CFIndex maxLen = CFStringGetMaximumSizeForEncoding(CFStringGetLength(v), kCFStringEncodingUTF8) + 1;
893 stringBuffer = (char*) malloc(maxLen);
894 CFStringGetCString(v, stringBuffer, maxLen, kCFStringEncodingUTF8);
895 pathString = stringBuffer;
896 }
897
898 CFStringRef vExpanded = MakeExpandedPath(pathString);
899 CFComparisonResult result = CFStringCompare (vExpanded, idString.get(), 0);
900 if (stringBuffer != NULL)
901 {
902 free(stringBuffer);
903 }
904
905 CFRelease (vExpanded);
906
907 if (result == 0)
908 {
909 CFArrayRemoveValueAtIndex (mtValue.get(), i);
910 found = true;
911 break;
912 }
913 }
914
915 if (found)
916 {
917 #ifndef NDEBUG
918 CFShow (mtValue.get());
919 #endif
920
921 CFPreferencesSetValue (KEYCHAIN_SYNC_KEY,
922 mtValue,
923 KEYCHAIN_SYNC_DOMAIN,
924 kCFPreferencesCurrentUser,
925 kCFPreferencesAnyHost);
926 CFPreferencesSynchronize (KEYCHAIN_SYNC_DOMAIN, kCFPreferencesCurrentUser, kCFPreferencesAnyHost);
927 }
928 }
929
930 void StorageManager::remove(const KeychainList &kcsToRemove, bool deleteDb)
931 {
932 StLock<Mutex>_(mMutex);
933
934 bool unsetDefault = false;
935 bool updateList = (!isAppSandboxed());
936
937 if (updateList)
938 {
939 mSavedList.revert(true);
940 DLDbIdentifier defaultId = mSavedList.defaultDLDbIdentifier();
941 for (KeychainList::const_iterator ix = kcsToRemove.begin();
942 ix != kcsToRemove.end(); ++ix)
943 {
944 // Find the keychain object for the given ref
945 Keychain theKeychain = *ix;
946 DLDbIdentifier dLDbIdentifier = theKeychain->dlDbIdentifier();
947
948 // Remove it from the saved list
949 mSavedList.remove(demungeDLDbIdentifier(dLDbIdentifier));
950 if (demungeDLDbIdentifier(dLDbIdentifier) == defaultId) {
951 unsetDefault=true;
952 }
953
954 if (deleteDb)
955 {
956 removeKeychainFromSyncList (dLDbIdentifier);
957
958 // Now remove it from the cache
959 removeKeychain(dLDbIdentifier, theKeychain.get());
960 }
961 }
962
963 if (unsetDefault)
964 mSavedList.defaultDLDbIdentifier(DLDbIdentifier());
965
966 mSavedList.save();
967 }
968
969 if (deleteDb)
970 {
971 // Delete the actual databases without holding any locks.
972 for (KeychainList::const_iterator ix = kcsToRemove.begin();
973 ix != kcsToRemove.end(); ++ix)
974 {
975 (*ix)->database()->deleteDb();
976 }
977 }
978
979 if (updateList) {
980 // Make sure we are not holding mLock when we post these events.
981 KCEventNotifier::PostKeychainEvent(kSecKeychainListChangedEvent);
982 }
983
984 if (unsetDefault)
985 KCEventNotifier::PostKeychainEvent(kSecDefaultChangedEvent);
986 }
987
988 void
989 StorageManager::getSearchList(KeychainList &keychainList)
990 {
991 // hold the global lock since we make keychain objects in this function
992
993 // to do: each of the items in this list must be retained, otherwise mayhem will occur
994 StLock<Mutex>_(mMutex);
995
996 if (gServerMode) {
997 keychainList.clear();
998 return;
999 }
1000
1001 mSavedList.revert(false);
1002 mCommonList.revert(false);
1003
1004 // Merge mSavedList, mDynamicList and mCommonList
1005 DLDbList dLDbList = mSavedList.searchList();
1006 DLDbList dynamicList = mDynamicList.searchList();
1007 DLDbList commonList = mCommonList.searchList();
1008 KeychainList result;
1009 result.reserve(dLDbList.size() + dynamicList.size() + commonList.size());
1010
1011 {
1012 for (DLDbList::const_iterator it = dynamicList.begin();
1013 it != dynamicList.end(); ++it)
1014 {
1015 Keychain k = keychain(*it);
1016 result.push_back(k);
1017 }
1018
1019 for (DLDbList::const_iterator it = dLDbList.begin();
1020 it != dLDbList.end(); ++it)
1021 {
1022 Keychain k = keychain(*it);
1023 result.push_back(k);
1024 }
1025
1026 for (DLDbList::const_iterator it = commonList.begin();
1027 it != commonList.end(); ++it)
1028 {
1029 Keychain k = keychain(*it);
1030 result.push_back(k);
1031 }
1032 }
1033
1034 keychainList.swap(result);
1035 }
1036
1037 void
1038 StorageManager::setSearchList(const KeychainList &keychainList)
1039 {
1040 StLock<Mutex>_(mMutex);
1041
1042 DLDbList searchList, oldSearchList(mSavedList.searchList());
1043 for (KeychainList::const_iterator it = keychainList.begin(); it != keychainList.end(); ++it)
1044 {
1045 DLDbIdentifier dldbi = demungeDLDbIdentifier((*it)->dlDbIdentifier());
1046
1047 // If this keychain is not in the common or dynamic lists, add it to the new search list
1048 DLDbList commonList = mCommonList.searchList();
1049 bool found = false;
1050 for(DLDbList::const_iterator jt = commonList.begin(); jt != commonList.end(); ++jt) {
1051 if((*jt) == dldbi) {
1052 found = true;
1053 }
1054 }
1055
1056 DLDbList dynamicList = mDynamicList.searchList();
1057 for(DLDbList::const_iterator jt = dynamicList.begin(); jt != dynamicList.end(); ++jt) {
1058 if((*jt) == dldbi) {
1059 found = true;
1060 }
1061 }
1062
1063 if(found) {
1064 continue;
1065 }
1066
1067 searchList.push_back(dldbi);
1068 }
1069
1070 {
1071 // Set the current searchlist to be what was passed in, the old list will be freed
1072 // upon exit of this stackframe.
1073 mSavedList.revert(true);
1074 mSavedList.searchList(searchList);
1075 mSavedList.save();
1076 }
1077
1078 if (!(oldSearchList == searchList))
1079 {
1080 // Make sure we are not holding mLock when we post this event.
1081 KCEventNotifier::PostKeychainEvent(kSecKeychainListChangedEvent);
1082 }
1083 }
1084
1085 void
1086 StorageManager::getSearchList(SecPreferencesDomain domain, KeychainList &keychainList)
1087 {
1088 StLock<Mutex>_(mMutex);
1089
1090 if (gServerMode) {
1091 keychainList.clear();
1092 return;
1093 }
1094
1095 if (domain == kSecPreferencesDomainDynamic)
1096 {
1097 convertList(keychainList, mDynamicList.searchList());
1098 }
1099 else if (domain == mDomain)
1100 {
1101 mSavedList.revert(false);
1102 convertList(keychainList, mSavedList.searchList());
1103 }
1104 else
1105 {
1106 convertList(keychainList, DLDbListCFPref(domain).searchList());
1107 }
1108 }
1109
1110 void StorageManager::forceUserSearchListReread()
1111 {
1112 mSavedList.forceUserSearchListReread();
1113 }
1114
1115 void
1116 StorageManager::setSearchList(SecPreferencesDomain domain, const KeychainList &keychainList)
1117 {
1118 StLock<Mutex>_(mMutex);
1119
1120 if (domain == kSecPreferencesDomainDynamic)
1121 MacOSError::throwMe(errSecInvalidPrefsDomain);
1122
1123 DLDbList searchList;
1124 convertList(searchList, keychainList);
1125
1126 if (domain == mDomain)
1127 {
1128 DLDbList oldSearchList(mSavedList.searchList());
1129 {
1130 // Set the current searchlist to be what was passed in, the old list will be freed
1131 // upon exit of this stackframe.
1132 mSavedList.revert(true);
1133 mSavedList.searchList(searchList);
1134 mSavedList.save();
1135 }
1136
1137 if (!(oldSearchList == searchList))
1138 {
1139 KCEventNotifier::PostKeychainEvent(kSecKeychainListChangedEvent);
1140 }
1141 }
1142 else
1143 {
1144 DLDbListCFPref(domain).searchList(searchList);
1145 }
1146 }
1147
1148 void
1149 StorageManager::domain(SecPreferencesDomain domain)
1150 {
1151 StLock<Mutex>_(mMutex);
1152
1153 if (domain == kSecPreferencesDomainDynamic)
1154 MacOSError::throwMe(errSecInvalidPrefsDomain);
1155
1156 if (domain == mDomain)
1157 return; // no change
1158
1159 #if !defined(NDEBUG)
1160 switch (domain)
1161 {
1162 case kSecPreferencesDomainSystem:
1163 secnotice("storagemgr", "switching to system domain"); break;
1164 case kSecPreferencesDomainUser:
1165 secnotice("storagemgr", "switching to user domain (uid %d)", getuid()); break;
1166 default:
1167 secnotice("storagemgr", "switching to weird prefs domain %d", domain); break;
1168 }
1169 #endif
1170
1171 mDomain = domain;
1172 mSavedList.set(domain);
1173 }
1174
1175 void
1176 StorageManager::optionalSearchList(CFTypeRef keychainOrArray, KeychainList &keychainList)
1177 {
1178 StLock<Mutex>_(mMutex);
1179
1180 if (!keychainOrArray)
1181 getSearchList(keychainList);
1182 else
1183 {
1184 CFTypeID typeID = CFGetTypeID(keychainOrArray);
1185 if (typeID == CFArrayGetTypeID())
1186 convertToKeychainList(CFArrayRef(keychainOrArray), keychainList);
1187 else if (typeID == gTypes().KeychainImpl.typeID)
1188 keychainList.push_back(KeychainImpl::required(SecKeychainRef(keychainOrArray)));
1189 else
1190 MacOSError::throwMe(errSecParam);
1191 }
1192 }
1193
1194 // static methods.
1195 void
1196 StorageManager::convertToKeychainList(CFArrayRef keychainArray, KeychainList &keychainList)
1197 {
1198 CFIndex count = CFArrayGetCount(keychainArray);
1199 if (!(count > 0))
1200 return;
1201
1202 KeychainList keychains(count);
1203 for (CFIndex ix = 0; ix < count; ++ix)
1204 {
1205 keychains[ix] = KeychainImpl::required(SecKeychainRef(CFArrayGetValueAtIndex(keychainArray, ix)));
1206 }
1207
1208 keychainList.swap(keychains);
1209 }
1210
1211 CFArrayRef
1212 StorageManager::convertFromKeychainList(const KeychainList &keychainList)
1213 {
1214 CFRef<CFMutableArrayRef> keychainArray(CFArrayCreateMutable(NULL, keychainList.size(), &kCFTypeArrayCallBacks));
1215
1216 for (KeychainList::const_iterator ix = keychainList.begin(); ix != keychainList.end(); ++ix)
1217 {
1218 SecKeychainRef keychainRef = (*ix)->handle();
1219 CFArrayAppendValue(keychainArray, keychainRef);
1220 CFRelease(keychainRef);
1221 }
1222
1223 // Counter the CFRelease that CFRef<> is about to do when keychainArray goes out of scope.
1224 CFRetain(keychainArray);
1225 return keychainArray;
1226 }
1227
1228 void StorageManager::convertList(DLDbList &ids, const KeychainList &kcs)
1229 {
1230 DLDbList result;
1231 result.reserve(kcs.size());
1232 for (KeychainList::const_iterator ix = kcs.begin(); ix != kcs.end(); ++ix)
1233 {
1234 result.push_back(demungeDLDbIdentifier((*ix)->dlDbIdentifier()));
1235 }
1236 ids.swap(result);
1237 }
1238
1239 void StorageManager::convertList(KeychainList &kcs, const DLDbList &ids)
1240 {
1241 StLock<Mutex>_(mMutex);
1242
1243 KeychainList result;
1244 result.reserve(ids.size());
1245 {
1246 for (DLDbList::const_iterator ix = ids.begin(); ix != ids.end(); ++ix)
1247 result.push_back(keychain(*ix));
1248 }
1249 kcs.swap(result);
1250 }
1251
1252 #pragma mark ____ Login Functions ____
1253
1254 void StorageManager::login(AuthorizationRef authRef, UInt32 nameLength, const char* name, bool isReset)
1255 {
1256 StLock<Mutex>_(mMutex);
1257
1258 AuthorizationItemSet* info = NULL;
1259 OSStatus result = AuthorizationCopyInfo(authRef, NULL, &info); // get the results of the copy rights call.
1260 Boolean created = false;
1261 if ( result == errSecSuccess && info->count )
1262 {
1263 // Grab the password from the auth context (info) and create the keychain...
1264 //
1265 AuthorizationItem* currItem = info->items;
1266 for (UInt32 index = 1; index <= info->count; index++) //@@@plugin bug won't return a specific context.
1267 {
1268 if (strcmp(currItem->name, kAuthorizationEnvironmentPassword) == 0)
1269 {
1270 // creates the login keychain with the specified password
1271 try
1272 {
1273 login(nameLength, name, (UInt32)currItem->valueLength, currItem->value, isReset);
1274 created = true;
1275 }
1276 catch(...)
1277 {
1278 }
1279 break;
1280 }
1281 currItem++;
1282 }
1283 }
1284 if ( info )
1285 AuthorizationFreeItemSet(info);
1286
1287 if ( !created )
1288 MacOSError::throwMe(errAuthorizationInternal);
1289 }
1290
1291 void StorageManager::login(ConstStringPtr name, ConstStringPtr password)
1292 {
1293 StLock<Mutex>_(mMutex);
1294
1295 if ( name == NULL || password == NULL )
1296 MacOSError::throwMe(errSecParam);
1297
1298 login(name[0], name + 1, password[0], password + 1, false);
1299 }
1300
1301 void StorageManager::login(UInt32 nameLength, const void *name,
1302 UInt32 passwordLength, const void *password, bool isReset)
1303 {
1304 if (passwordLength != 0 && password == NULL)
1305 {
1306 secnotice("KCLogin", "StorageManager::login: invalid argument (NULL password)");
1307 MacOSError::throwMe(errSecParam);
1308 }
1309
1310 DLDbIdentifier loginDLDbIdentifier;
1311 {
1312 mSavedList.revert(true);
1313 loginDLDbIdentifier = mSavedList.loginDLDbIdentifier();
1314 }
1315
1316 secnotice("KCLogin", "StorageManager::login: loginDLDbIdentifier is %s", (loginDLDbIdentifier) ? loginDLDbIdentifier.dbName() : "<NULL>");
1317 if (!loginDLDbIdentifier)
1318 MacOSError::throwMe(errSecNoSuchKeychain);
1319
1320
1321 //***************************************************************
1322 // gather keychain information
1323 //***************************************************************
1324
1325 // user name
1326 int uid = geteuid();
1327 struct passwd *pw = getpwuid(uid);
1328 if (pw == NULL) {
1329 secnotice("KCLogin", "StorageManager::login: invalid argument (NULL uid)");
1330 MacOSError::throwMe(errSecParam);
1331 }
1332 char *userName = pw->pw_name;
1333
1334 // make keychain path strings
1335 std::string keychainPath = DLDbListCFPref::ExpandTildesInPath(kLoginKeychainPathPrefix);
1336 std::string shortnameKeychain = keychainPath + userName;
1337 std::string shortnameDotKeychain = shortnameKeychain + ".keychain";
1338 std::string loginDotKeychain = keychainPath + "login.keychain";
1339 std::string loginRenamed1Keychain = keychainPath + "login_renamed1.keychain";
1340 std::string loginKeychainDb = keychainPath + "login.keychain-db";
1341
1342 // check for existence of keychain files
1343 bool shortnameKeychainExists = false;
1344 bool shortnameDotKeychainExists = false;
1345 bool loginKeychainExists = false;
1346 bool loginRenamed1KeychainExists = false;
1347 bool loginKeychainDbExists = false;
1348 {
1349 struct stat st;
1350 int stat_result;
1351 stat_result = ::stat(shortnameKeychain.c_str(), &st);
1352 shortnameKeychainExists = (stat_result == 0);
1353 stat_result = ::stat(shortnameDotKeychain.c_str(), &st);
1354 shortnameDotKeychainExists = (stat_result == 0);
1355 stat_result = ::stat(loginDotKeychain.c_str(), &st);
1356 loginKeychainExists = (stat_result == 0);
1357 stat_result = ::stat(loginRenamed1Keychain.c_str(), &st);
1358 loginRenamed1KeychainExists = (stat_result == 0);
1359 stat_result = ::stat(loginKeychainDb.c_str(), &st);
1360 loginKeychainDbExists = (stat_result == 0);
1361 }
1362
1363 // login.keychain-db is considered to be the same as login.keychain.
1364 // Our transparent keychain promotion on open will handle opening the right version of this file.
1365 loginKeychainExists |= loginKeychainDbExists;
1366
1367 bool loginUnlocked = false;
1368
1369 // make the keychain identifiers
1370 CSSM_VERSION version = {0, 0};
1371 DLDbIdentifier shortnameDLDbIdentifier = DLDbListCFPref::makeDLDbIdentifier(gGuidAppleCSPDL, version, 0, CSSM_SERVICE_CSP | CSSM_SERVICE_DL, shortnameKeychain.c_str(), NULL);
1372 DLDbIdentifier shortnameDotDLDbIdentifier = DLDbListCFPref::makeDLDbIdentifier(gGuidAppleCSPDL, version, 0, CSSM_SERVICE_CSP | CSSM_SERVICE_DL, shortnameDotKeychain.c_str(), NULL);
1373 DLDbIdentifier loginRenamed1DLDbIdentifier = DLDbListCFPref::makeDLDbIdentifier(gGuidAppleCSPDL, version, 0, CSSM_SERVICE_CSP | CSSM_SERVICE_DL, loginRenamed1Keychain.c_str(), NULL);
1374
1375 //***************************************************************
1376 // make file renaming changes first
1377 //***************************************************************
1378
1379 // if "~/Library/Keychains/shortname" exists, we need to migrate it forward;
1380 // either to login.keychain if there isn't already one, otherwise to shortname.keychain
1381 if (shortnameKeychainExists) {
1382 int rename_stat = 0;
1383 if (loginKeychainExists) {
1384 struct stat st;
1385 int tmp_result = ::stat(loginDotKeychain.c_str(), &st);
1386 if (tmp_result == 0) {
1387 if (st.st_size <= kEmptyKeychainSizeInBytes) {
1388 tmp_result = ::unlink(loginDotKeychain.c_str());
1389 rename_stat = ::rename(shortnameKeychain.c_str(), loginDotKeychain.c_str());
1390 shortnameKeychainExists = (rename_stat != 0);
1391 }
1392 }
1393 }
1394 if (shortnameKeychainExists) {
1395 if (loginKeychainExists && !shortnameDotKeychainExists) {
1396 rename_stat = ::rename(shortnameKeychain.c_str(), shortnameDotKeychain.c_str());
1397 shortnameDotKeychainExists = (rename_stat == 0);
1398 } else if (!loginKeychainExists) {
1399 rename_stat = ::rename(shortnameKeychain.c_str(), loginDotKeychain.c_str());
1400 loginKeychainExists = (rename_stat == 0);
1401 } else {
1402 // we have all 3 keychains: login.keychain, shortname, and shortname.keychain.
1403 // on Leopard we never want a shortname keychain, so we must move it aside.
1404 char pathbuf[MAXPATHLEN];
1405 std::string shortnameRenamedXXXKeychain = keychainPath;
1406 shortnameRenamedXXXKeychain += userName;
1407 shortnameRenamedXXXKeychain += "_renamed_XXX.keychain";
1408 ::strlcpy(pathbuf, shortnameRenamedXXXKeychain.c_str(), sizeof(pathbuf));
1409 ::mkstemps(pathbuf, 9); // 9 == strlen(".keychain")
1410 rename_stat = ::rename(shortnameKeychain.c_str(), pathbuf);
1411 shortnameKeychainExists = (rename_stat != 0);
1412 }
1413 }
1414 if (rename_stat != 0) {
1415 MacOSError::throwMe(errno);
1416 }
1417 }
1418
1419 //***************************************************************
1420 // handle special case where user previously reset the keychain
1421 //***************************************************************
1422 // Since 9A581, we have changed the definition of kKeychainRenamedSuffix from "_renamed" to "_renamed_".
1423 // Therefore, if "login_renamed1.keychain" exists and there is no plist, the user may have run into a
1424 // prior upgrade issue and clicked Reset. If we can successfully unlock login_renamed1.keychain with the
1425 // supplied password, then we will attempt to rename it to login.keychain if that file is empty, or with
1426 // "shortname.keychain" if it is not.
1427
1428 if (loginRenamed1KeychainExists && (!loginKeychainExists ||
1429 (mSavedList.searchList().size() == 1 && mSavedList.member(demungeDLDbIdentifier(loginDLDbIdentifier))) )) {
1430 try
1431 {
1432 Keychain loginRenamed1KC(keychain(loginRenamed1DLDbIdentifier));
1433 secnotice("KCLogin", "Attempting to unlock renamed KC \"%s\"",
1434 (loginRenamed1KC) ? loginRenamed1KC->name() : "<NULL>");
1435 loginRenamed1KC->unlock(CssmData(const_cast<void *>(password), passwordLength));
1436 // if we get here, we unlocked it
1437 if (loginKeychainExists) {
1438 struct stat st;
1439 int tmp_result = ::stat(loginDotKeychain.c_str(), &st);
1440 if (tmp_result == 0) {
1441 if (st.st_size <= kEmptyKeychainSizeInBytes) {
1442 tmp_result = ::unlink(loginDotKeychain.c_str());
1443 tmp_result = ::rename(loginRenamed1Keychain.c_str(), loginDotKeychain.c_str());
1444 } else if (!shortnameDotKeychainExists) {
1445 tmp_result = ::rename(loginRenamed1Keychain.c_str(), shortnameDotKeychain.c_str());
1446 shortnameDotKeychainExists = (tmp_result == 0);
1447 } else {
1448 throw 1; // can't do anything with it except move it out of the way
1449 }
1450 }
1451 } else {
1452 int tmp_result = ::rename(loginRenamed1Keychain.c_str(), loginDotKeychain.c_str());
1453 loginKeychainExists = (tmp_result == 0);
1454 }
1455 }
1456 catch(...)
1457 {
1458 // we failed to unlock the login_renamed1.keychain file with the login password.
1459 // move it aside so we don't try to deal with it again.
1460 char pathbuf[MAXPATHLEN];
1461 std::string loginRenamedXXXKeychain = keychainPath;
1462 loginRenamedXXXKeychain += "login_renamed_XXX.keychain";
1463 ::strlcpy(pathbuf, loginRenamedXXXKeychain.c_str(), sizeof(pathbuf));
1464 ::mkstemps(pathbuf, 9); // 9 == strlen(".keychain")
1465 ::rename(loginRenamed1Keychain.c_str(), pathbuf);
1466 }
1467 }
1468
1469 // if login.keychain does not exist at this point, create it
1470 if (!loginKeychainExists || (isReset && !loginKeychainDbExists)) {
1471 // but don't add it to the search list yet; we'll do that later
1472 Keychain theKeychain = makeKeychain(loginDLDbIdentifier, false, true);
1473 secnotice("KCLogin", "Creating login keychain %s", (loginDLDbIdentifier) ? loginDLDbIdentifier.dbName() : "<NULL>");
1474 theKeychain->create(passwordLength, password);
1475 secnotice("KCLogin", "Login keychain created successfully");
1476 loginKeychainExists = true;
1477 // Set the prefs for this new login keychain.
1478 loginKeychain(theKeychain);
1479 // Login Keychain does not lock on sleep nor lock after timeout by default.
1480 theKeychain->setSettings(INT_MAX, false);
1481 loginUnlocked = true;
1482 mSavedList.revert(true);
1483 }
1484
1485 //***************************************************************
1486 // make plist changes after files have been renamed or created
1487 //***************************************************************
1488
1489 // if the shortname keychain exists in the search list, either rename or remove the entry
1490 if (mSavedList.member(demungeDLDbIdentifier(shortnameDLDbIdentifier))) {
1491 if (shortnameDotKeychainExists && !mSavedList.member(demungeDLDbIdentifier(shortnameDotDLDbIdentifier))) {
1492 // change shortname to shortname.keychain (login.keychain will be added later if not present)
1493 secnotice("KCLogin", "Renaming %s to %s in keychain search list",
1494 (shortnameDLDbIdentifier) ? shortnameDLDbIdentifier.dbName() : "<NULL>",
1495 (shortnameDotDLDbIdentifier) ? shortnameDotDLDbIdentifier.dbName() : "<NULL>");
1496 mSavedList.rename(demungeDLDbIdentifier(shortnameDLDbIdentifier),
1497 demungeDLDbIdentifier(shortnameDotDLDbIdentifier));
1498 } else if (!mSavedList.member(demungeDLDbIdentifier(loginDLDbIdentifier))) {
1499 // change shortname to login.keychain
1500 secnotice("KCLogin", "Renaming %s to %s in keychain search list",
1501 (shortnameDLDbIdentifier) ? shortnameDLDbIdentifier.dbName() : "<NULL>",
1502 (loginDLDbIdentifier) ? loginDLDbIdentifier.dbName() : "<NULL>");
1503 mSavedList.rename(demungeDLDbIdentifier(shortnameDLDbIdentifier),
1504 demungeDLDbIdentifier(loginDLDbIdentifier));
1505 } else {
1506 // already have login.keychain in list, and renaming to shortname.keychain isn't an option,
1507 // so just remove the entry
1508 secnotice("KCLogin", "Removing %s from keychain search list", (shortnameDLDbIdentifier) ? shortnameDLDbIdentifier.dbName() : "<NULL>");
1509 mSavedList.remove(demungeDLDbIdentifier(shortnameDLDbIdentifier));
1510 }
1511
1512 // note: save() will cause the plist to be unlinked if the only remaining entry is for login.keychain
1513 mSavedList.save();
1514 mSavedList.revert(true);
1515 }
1516
1517 // make sure that login.keychain is in the search list
1518 if (!mSavedList.member(demungeDLDbIdentifier(loginDLDbIdentifier))) {
1519 secnotice("KCLogin", "Adding %s to keychain search list", (loginDLDbIdentifier) ? loginDLDbIdentifier.dbName() : "<NULL>");
1520 mSavedList.add(demungeDLDbIdentifier(loginDLDbIdentifier));
1521 mSavedList.save();
1522 mSavedList.revert(true);
1523 }
1524
1525 // if we have a shortname.keychain, always include it in the plist (after login.keychain)
1526 if (shortnameDotKeychainExists && !mSavedList.member(demungeDLDbIdentifier(shortnameDotDLDbIdentifier))) {
1527 mSavedList.add(demungeDLDbIdentifier(shortnameDotDLDbIdentifier));
1528 mSavedList.save();
1529 mSavedList.revert(true);
1530 }
1531
1532 // make sure that the default keychain is in the search list; if not, reset the default to login.keychain
1533 if (!mSavedList.member(mSavedList.defaultDLDbIdentifier())) {
1534 secnotice("KCLogin", "Changing default keychain to %s", (loginDLDbIdentifier) ? loginDLDbIdentifier.dbName() : "<NULL>");
1535 mSavedList.defaultDLDbIdentifier(demungeDLDbIdentifier(loginDLDbIdentifier));
1536 mSavedList.save();
1537 mSavedList.revert(true);
1538 }
1539
1540 //***************************************************************
1541 // auto-unlock the login keychain(s)
1542 //***************************************************************
1543 // all our preflight fixups are finally done, so we can now attempt to unlock the login keychain
1544
1545 OSStatus loginResult = errSecSuccess;
1546 if (!loginUnlocked) {
1547 try
1548 {
1549 Keychain theKeychain(keychain(loginDLDbIdentifier));
1550 secnotice("KCLogin", "Attempting to unlock login keychain \"%s\"",
1551 (theKeychain) ? theKeychain->name() : "<NULL>");
1552 theKeychain->unlock(CssmData(const_cast<void *>(password), passwordLength));
1553 loginUnlocked = true;
1554 }
1555 catch(const CssmError &e)
1556 {
1557 loginResult = e.osStatus(); // save this result
1558 }
1559 }
1560
1561 // is it token login?
1562 CFRef<CFDictionaryRef> tokenLoginContext;
1563 OSStatus status = TokenLoginGetContext(password, passwordLength, tokenLoginContext.take());
1564 if (!loginUnlocked || status == errSecSuccess) {
1565 Keychain theKeychain(keychain(loginDLDbIdentifier));
1566 bool tokenLoginDataUpdated = false;
1567
1568 for (UInt32 i = 0; i < 2; i++) {
1569 loginResult = errSecSuccess;
1570
1571 CFRef<CFDictionaryRef> tokenLoginData;
1572 if (tokenLoginContext) {
1573 status = TokenLoginGetLoginData(tokenLoginContext, tokenLoginData.take());
1574 if (status != errSecSuccess) {
1575 if (tokenLoginDataUpdated) {
1576 loginResult = status;
1577 break;
1578 }
1579 // updating unlock key fails if it is not token login
1580 secnotice("KCLogin", "Error %d, reconstructing unlock data", (int)status);
1581 status = TokenLoginUpdateUnlockData(tokenLoginContext);
1582 if (status == errSecSuccess) {
1583 loginResult = TokenLoginGetLoginData(tokenLoginContext, tokenLoginData.take());
1584 if (loginResult != errSecSuccess) {
1585 break;
1586 }
1587 tokenLoginDataUpdated = true;
1588 }
1589 }
1590 }
1591
1592 try {
1593 // first try to unlock login keychain because if this fails, token keychain unlock fails as well
1594 if (tokenLoginData) {
1595 secnotice("KCLogin", "Going to unlock keybag using scBlob");
1596 status = TokenLoginUnlockKeybag(tokenLoginContext, tokenLoginData);
1597 secnotice("KCLogin", "Keybag unlock result %d", (int)status);
1598 if (status)
1599 CssmError::throwMe(status); // to trigger login data regeneration
1600 }
1601
1602 // build a fake key
1603 CssmKey key;
1604 key.header().BlobType = CSSM_KEYBLOB_RAW;
1605 key.header().Format = CSSM_KEYBLOB_RAW_FORMAT_OCTET_STRING;
1606 key.header().AlgorithmId = CSSM_ALGID_3DES_3KEY;
1607 key.header().KeyClass = CSSM_KEYCLASS_SESSION_KEY;
1608 key.header().KeyUsage = CSSM_KEYUSE_ENCRYPT | CSSM_KEYUSE_DECRYPT | CSSM_KEYATTR_EXTRACTABLE;
1609 key.header().KeyAttr = 0;
1610 CFRef<CFDataRef> tokenLoginUnlockKey;
1611 if (tokenLoginData) {
1612 status = TokenLoginGetUnlockKey(tokenLoginContext, tokenLoginUnlockKey.take());
1613 if (status)
1614 CssmError::throwMe(status); // to trigger login data regeneration
1615 key.KeyData = CssmData(tokenLoginUnlockKey.get());
1616 } else {
1617 key.KeyData = CssmData(const_cast<void *>(password), passwordLength);
1618 }
1619 // unwrap it into the CSP (but keep it raw)
1620 UnwrapKey unwrap(theKeychain->csp(), CSSM_ALGID_NONE);
1621 CssmKey masterKey;
1622 CssmData descriptiveData;
1623 unwrap(key,
1624 KeySpec(CSSM_KEYUSE_ANY, CSSM_KEYATTR_EXTRACTABLE),
1625 masterKey, &descriptiveData, NULL);
1626
1627 CssmClient::Db db = theKeychain->database();
1628
1629 // create the keychain, using appropriate credentials
1630 Allocator &alloc = db->allocator();
1631 AutoCredentials cred(alloc); // will leak, but we're quitting soon :-)
1632
1633 // use this passphrase
1634 cred += TypedList(alloc, CSSM_SAMPLE_TYPE_KEYCHAIN_LOCK,
1635 new(alloc) ListElement(CSSM_SAMPLE_TYPE_SYMMETRIC_KEY),
1636 new(alloc) ListElement(CssmData::wrap(theKeychain->csp()->handle())),
1637 new(alloc) ListElement(CssmData::wrap(masterKey)),
1638 new(alloc) ListElement(CssmData()));
1639 db->authenticate(CSSM_DB_ACCESS_READ, &cred);
1640 db->unlock();
1641 loginUnlocked = true;
1642 } catch (const CssmError &e) {
1643 if (tokenLoginData && !tokenLoginDataUpdated) {
1644 // token login unlock key was invalid
1645 loginResult = TokenLoginUpdateUnlockData(tokenLoginContext);
1646 if (loginResult == errSecSuccess) {
1647 tokenLoginDataUpdated = true;
1648 continue;
1649 }
1650 }
1651 else {
1652 loginResult = e.osStatus();
1653 }
1654 }
1655 break;
1656 }
1657 }
1658
1659 // if "shortname.keychain" exists and is in the search list, attempt to auto-unlock it with the same password
1660 if (shortnameDotKeychainExists && mSavedList.member(demungeDLDbIdentifier(shortnameDotDLDbIdentifier))) {
1661 try
1662 {
1663 Keychain shortnameDotKC(keychain(shortnameDotDLDbIdentifier));
1664 secnotice("KCLogin", "Attempting to unlock short name keychain \"%s\"",
1665 (shortnameDotKC) ? shortnameDotKC->name() : "<NULL>");
1666 shortnameDotKC->unlock(CssmData(const_cast<void *>(password), passwordLength));
1667 }
1668 catch(const CssmError &e)
1669 {
1670 // ignore; failure to unlock this keychain is not considered an error
1671 }
1672 }
1673
1674 if (loginResult != errSecSuccess) {
1675 MacOSError::throwMe(loginResult);
1676 }
1677 }
1678
1679 void StorageManager::stashLogin()
1680 {
1681 OSStatus loginResult = errSecSuccess;
1682
1683 DLDbIdentifier loginDLDbIdentifier;
1684 {
1685 mSavedList.revert(true);
1686 loginDLDbIdentifier = mSavedList.loginDLDbIdentifier();
1687 }
1688
1689 secnotice("KCLogin", "StorageManager::stash: loginDLDbIdentifier is %s", (loginDLDbIdentifier) ? loginDLDbIdentifier.dbName() : "<NULL>");
1690 if (!loginDLDbIdentifier)
1691 MacOSError::throwMe(errSecNoSuchKeychain);
1692
1693 try
1694 {
1695 CssmData empty;
1696 Keychain theKeychain(keychain(loginDLDbIdentifier));
1697 secnotice("KCLogin", "Attempting to use stash for login keychain \"%s\"",
1698 (theKeychain) ? theKeychain->name() : "<NULL>");
1699 theKeychain->stashCheck();
1700 }
1701 catch(const CssmError &e)
1702 {
1703 loginResult = e.osStatus(); // save this result
1704 }
1705
1706
1707 if (loginResult != errSecSuccess) {
1708 MacOSError::throwMe(loginResult);
1709 }
1710 }
1711
1712 void StorageManager::stashKeychain()
1713 {
1714 OSStatus loginResult = errSecSuccess;
1715
1716 DLDbIdentifier loginDLDbIdentifier;
1717 {
1718 mSavedList.revert(true);
1719 loginDLDbIdentifier = mSavedList.loginDLDbIdentifier();
1720 }
1721
1722 secnotice("KCLogin", "StorageManager::stash: loginDLDbIdentifier is %s", (loginDLDbIdentifier) ? loginDLDbIdentifier.dbName() : "<NULL>");
1723 if (!loginDLDbIdentifier)
1724 MacOSError::throwMe(errSecNoSuchKeychain);
1725
1726 try
1727 {
1728 Keychain theKeychain(keychain(loginDLDbIdentifier));
1729 secnotice("KCLogin", "Attempting to stash login keychain \"%s\"",
1730 (theKeychain) ? theKeychain->name() : "<NULL>");
1731 theKeychain->stash();
1732 }
1733 catch(const CssmError &e)
1734 {
1735 loginResult = e.osStatus(); // save this result
1736 }
1737
1738
1739 if (loginResult != errSecSuccess) {
1740 MacOSError::throwMe(loginResult);
1741 }
1742 }
1743
1744 void StorageManager::logout()
1745 {
1746 // nothing left to do here
1747 }
1748
1749 void StorageManager::changeLoginPassword(ConstStringPtr oldPassword, ConstStringPtr newPassword)
1750 {
1751 StLock<Mutex>_(mMutex);
1752
1753 loginKeychain()->changePassphrase(oldPassword, newPassword);
1754 secnotice("KClogin", "Changed login keychain password successfully");
1755 }
1756
1757
1758 void StorageManager::changeLoginPassword(UInt32 oldPasswordLength, const void *oldPassword, UInt32 newPasswordLength, const void *newPassword)
1759 {
1760 StLock<Mutex>_(mMutex);
1761
1762 loginKeychain()->changePassphrase(oldPasswordLength, oldPassword, newPasswordLength, newPassword);
1763 secnotice("KClogin", "Changed login keychain password successfully");
1764 }
1765
1766 // Clear out the keychain search list and rename the existing login.keychain.
1767 //
1768 void StorageManager::resetKeychain(Boolean resetSearchList)
1769 {
1770 StLock<Mutex>_(mMutex);
1771
1772 // Clear the keychain search list.
1773 try
1774 {
1775 if ( resetSearchList )
1776 {
1777 StorageManager::KeychainList keychainList;
1778 setSearchList(keychainList);
1779 }
1780 // Get a reference to the existing login keychain...
1781 // If we don't have one, we throw (not requiring a rename).
1782 //
1783 Keychain keychain = loginKeychain();
1784 //
1785 // Rename the existing login.keychain (i.e. put it aside).
1786 //
1787 CFMutableStringRef newName = NULL;
1788 newName = CFStringCreateMutable(NULL, 0);
1789 CFStringRef currName = NULL;
1790 currName = CFStringCreateWithCString(NULL, keychain->name(), kCFStringEncodingUTF8);
1791 if ( newName && currName )
1792 {
1793 CFStringAppend(newName, currName);
1794 CFStringRef kcSuffix = CFSTR(kKeychainSuffix);
1795 CFStringRef kcDbSuffix = CFSTR(kKeychainDbSuffix);
1796 bool hasDbSuffix = false;
1797 if ( CFStringHasSuffix(newName, kcSuffix) ) // remove the .keychain extension
1798 {
1799 CFRange suffixRange = CFStringFind(newName, kcSuffix, 0);
1800 CFStringFindAndReplace(newName, kcSuffix, CFSTR(""), suffixRange, 0);
1801 }
1802 if (CFStringHasSuffix(newName, kcDbSuffix)) {
1803 hasDbSuffix = true;
1804 CFRange suffixRange = CFStringFind(newName, kcDbSuffix, 0);
1805 CFStringFindAndReplace(newName, kcDbSuffix, CFSTR(""), suffixRange, 0);
1806 }
1807
1808 CFStringAppend(newName, CFSTR(kKeychainRenamedSuffix)); // add "_renamed_"
1809 try
1810 {
1811 renameUnique(keychain, newName, hasDbSuffix);
1812 }
1813 catch(...)
1814 {
1815 // we need to release 'newName' & 'currName'
1816 }
1817 } // else, let the login call report a duplicate
1818 if ( newName )
1819 CFRelease(newName);
1820 if ( currName )
1821 CFRelease(currName);
1822 }
1823 catch(...)
1824 {
1825 // We either don't have a login keychain, or there was a
1826 // failure to rename the existing one.
1827 }
1828 }
1829
1830 #pragma mark ____ File Related ____
1831
1832 Keychain StorageManager::make(const char *pathName)
1833 {
1834 return make(pathName, true);
1835 }
1836
1837 Keychain StorageManager::make(const char *pathName, bool add)
1838 {
1839 return make(pathName, add, false);
1840 }
1841
1842 Keychain StorageManager::make(const char *pathName, bool add, bool isReset) {
1843 return makeKeychain(makeDLDbIdentifier(pathName), add, isReset);
1844 }
1845
1846 DLDbIdentifier StorageManager::makeDLDbIdentifier(const char *pathName) {
1847 StLock<Mutex>_(mMutex);
1848
1849 string fullPathName;
1850 if ( pathName[0] == '/' )
1851 fullPathName = pathName;
1852 else
1853 {
1854 // Get Home directory from environment.
1855 switch (mDomain)
1856 {
1857 case kSecPreferencesDomainUser:
1858 {
1859 const char *homeDir = getenv("HOME");
1860 if (homeDir == NULL)
1861 {
1862 // If $HOME is unset get the current user's home directory
1863 // from the passwd file.
1864 uid_t uid = geteuid();
1865 if (!uid) uid = getuid();
1866 struct passwd *pw = getpwuid(uid);
1867 if (!pw)
1868 MacOSError::throwMe(errSecParam);
1869 homeDir = pw->pw_dir;
1870 }
1871 fullPathName = homeDir;
1872 }
1873 break;
1874 case kSecPreferencesDomainSystem:
1875 fullPathName = "";
1876 break;
1877 default:
1878 assert(false); // invalid domain for this
1879 }
1880
1881 fullPathName += "/Library/Keychains/";
1882 fullPathName += pathName;
1883 }
1884
1885 const CSSM_NET_ADDRESS *DbLocation = NULL; // NULL for keychains
1886 const CSSM_VERSION *version = NULL;
1887 uint32 subserviceId = 0;
1888 CSSM_SERVICE_TYPE subserviceType = CSSM_SERVICE_DL | CSSM_SERVICE_CSP;
1889 const CssmSubserviceUid ssuid(gGuidAppleCSPDL, version,
1890 subserviceId, subserviceType);
1891 DLDbIdentifier dlDbIdentifier(ssuid, fullPathName.c_str(), DbLocation);
1892 return dlDbIdentifier;
1893 }
1894
1895 Keychain StorageManager::makeLoginAuthUI(const Item *item, bool isReset)
1896 {
1897 StLock<Mutex>_(mMutex);
1898
1899 // Create a login/default keychain for the user using UI.
1900 // The user can cancel out of the operation, or create a new login keychain.
1901 // If auto-login is turned off, the user will be asked for their login password.
1902 //
1903 OSStatus result = errSecSuccess;
1904 Keychain keychain; // We return this keychain.
1905 //
1906 // Set up the Auth ref to bring up UI.
1907 //
1908 AuthorizationItem *currItem, *authEnvirItemArrayPtr = NULL;
1909 AuthorizationRef authRef = NULL;
1910 try
1911 {
1912 result = AuthorizationCreate(NULL, NULL, kAuthorizationFlagDefaults, &authRef);
1913 if ( result )
1914 MacOSError::throwMe(result);
1915
1916 AuthorizationEnvironment envir;
1917 envir.count = 6; // up to 6 hints can be used.
1918 authEnvirItemArrayPtr = (AuthorizationItem*)malloc(sizeof(AuthorizationItem) * envir.count);
1919 if ( !authEnvirItemArrayPtr )
1920 MacOSError::throwMe(errAuthorizationInternal);
1921
1922 currItem = envir.items = authEnvirItemArrayPtr;
1923
1924 //
1925 // 1st Hint (optional): The keychain item's account attribute string.
1926 // When item is specified, we assume an 'add' operation is being attempted.
1927 char buff[256];
1928 UInt32 actLen = 0;
1929 SecKeychainAttribute attr = { kSecAccountItemAttr, 255, &buff };
1930 if ( item )
1931 {
1932 try
1933 {
1934 (*item)->getAttribute(attr, &actLen);
1935 }
1936 catch(...)
1937 {
1938 actLen = 0; // This item didn't have the account attribute, so don't display one in the UI.
1939 }
1940 }
1941 currItem->name = AGENT_HINT_ATTR_NAME; // name str that identifies this hint as attr name
1942 if ( actLen ) // Fill in the hint if we have an account attr
1943 {
1944 if ( actLen >= sizeof(buff) )
1945 buff[sizeof(buff)-1] = 0;
1946 else
1947 buff[actLen] = 0;
1948 currItem->valueLength = strlen(buff)+1;
1949 currItem->value = buff;
1950 }
1951 else
1952 {
1953 currItem->valueLength = 0;
1954 currItem->value = NULL;
1955 }
1956 currItem->flags = 0;
1957
1958 //
1959 // 2nd Hint (optional): The item's keychain full path.
1960 //
1961 currItem++;
1962 char* currDefaultName = NULL;
1963 try
1964 {
1965 currDefaultName = (char*)defaultKeychain()->name(); // Use the name if we have it.
1966 currItem->name = AGENT_HINT_LOGIN_KC_NAME; // Name str that identifies this hint as kc path
1967 currItem->valueLength = (currDefaultName) ? strlen(currDefaultName) : 0;
1968 currItem->value = (currDefaultName) ? (void*)currDefaultName : (void*)"";
1969 currItem->flags = 0;
1970 currItem++;
1971 }
1972 catch(...)
1973 {
1974 envir.count--;
1975 }
1976
1977 //
1978 // 3rd Hint (required): check if curr default keychain is unavailable.
1979 // This is determined by the parent not existing.
1980 //
1981 currItem->name = AGENT_HINT_LOGIN_KC_EXISTS_IN_KC_FOLDER;
1982 Boolean loginUnavail = false;
1983 try
1984 {
1985 Keychain defaultKC = defaultKeychain();
1986 if ( !defaultKC->exists() )
1987 loginUnavail = true;
1988 }
1989 catch(...) // login.keychain not present
1990 {
1991 }
1992 currItem->valueLength = sizeof(Boolean);
1993 currItem->value = (void*)&loginUnavail;
1994 currItem->flags = 0;
1995
1996 //
1997 // 4th Hint (required): userName
1998 //
1999 currItem++;
2000 currItem->name = AGENT_HINT_LOGIN_KC_USER_NAME;
2001 char* uName = getenv("USER");
2002 string userName = uName ? uName : "";
2003 if ( userName.length() == 0 )
2004 {
2005 uid_t uid = geteuid();
2006 if (!uid) uid = getuid();
2007 struct passwd *pw = getpwuid(uid); // fallback case...
2008 if (pw)
2009 userName = pw->pw_name;
2010 endpwent();
2011 }
2012 if ( userName.length() == 0 ) // did we ultimately get one?
2013 MacOSError::throwMe(errAuthorizationInternal);
2014
2015 currItem->value = (void*)userName.c_str();
2016 currItem->valueLength = userName.length();
2017 currItem->flags = 0;
2018
2019 //
2020 // 5th Hint (required): flags if user has more than 1 keychain (used for a later warning when reset to default).
2021 //
2022 currItem++;
2023 currItem->name = AGENT_HINT_LOGIN_KC_USER_HAS_OTHER_KCS_STR;
2024 Boolean moreThanOneKCExists = false;
2025 {
2026 // if item is NULL, then this is a user-initiated full reset
2027 if (item && mSavedList.searchList().size() > 1)
2028 moreThanOneKCExists = true;
2029 }
2030 currItem->value = &moreThanOneKCExists;
2031 currItem->valueLength = sizeof(Boolean);
2032 currItem->flags = 0;
2033
2034 //
2035 // 6th Hint (required): If no item is involved, this is a user-initiated full reset.
2036 // We want to suppress the "do you want to reset to defaults?" panel in this case.
2037 //
2038 currItem++;
2039 currItem->name = AGENT_HINT_LOGIN_KC_SUPPRESS_RESET_PANEL;
2040 Boolean suppressResetPanel = (item == NULL) ? TRUE : FALSE;
2041 currItem->valueLength = sizeof(Boolean);
2042 currItem->value = (void*)&suppressResetPanel;
2043 currItem->flags = 0;
2044
2045 //
2046 // Set up the auth rights and make the auth call.
2047 //
2048 AuthorizationItem authItem = { LOGIN_KC_CREATION_RIGHT, 0 , NULL, 0 };
2049 AuthorizationRights rights = { 1, &authItem };
2050 AuthorizationFlags flags = kAuthorizationFlagDefaults | kAuthorizationFlagInteractionAllowed | kAuthorizationFlagExtendRights;
2051 result = AuthorizationCopyRights(authRef, &rights, &envir, flags, NULL);
2052 if ( result )
2053 MacOSError::throwMe(result);
2054 try
2055 {
2056 resetKeychain(true); // Clears the plist, moves aside existing login.keychain
2057 }
2058 catch (...) // can throw if no existing login.keychain is found
2059 {
2060 }
2061 login(authRef, (UInt32)userName.length(), userName.c_str(), isReset); // Create login.keychain
2062 keychain = loginKeychain(); // Get newly-created login keychain
2063 defaultKeychain(keychain); // Set it to be the default
2064
2065 free(authEnvirItemArrayPtr);
2066 AuthorizationFree(authRef, kAuthorizationFlagDefaults);
2067 }
2068
2069 catch (...)
2070 {
2071 // clean up allocations, then rethrow error
2072 if ( authEnvirItemArrayPtr )
2073 free(authEnvirItemArrayPtr);
2074 if ( authRef )
2075 AuthorizationFree(authRef, kAuthorizationFlagDefaults);
2076 throw;
2077 }
2078
2079 return keychain;
2080 }
2081
2082 Keychain StorageManager::defaultKeychainUI(Item &item)
2083 {
2084 StLock<Mutex>_(mMutex);
2085
2086 Keychain returnedKeychain;
2087 try
2088 {
2089 returnedKeychain = defaultKeychain(); // If we have one, return it.
2090 if ( returnedKeychain->exists() )
2091 return returnedKeychain;
2092 }
2093 catch(...) // We could have one, but it isn't available (i.e. on a un-mounted volume).
2094 {
2095 }
2096 if ( globals().getUserInteractionAllowed() )
2097 {
2098 returnedKeychain = makeLoginAuthUI(&item, false); // If no Keychains is present, one will be created.
2099 if ( !returnedKeychain )
2100 MacOSError::throwMe(errSecInvalidKeychain); // Something went wrong...
2101 }
2102 else
2103 MacOSError::throwMe(errSecInteractionNotAllowed); // If UI isn't allowed, return an error.
2104
2105 return returnedKeychain;
2106 }
2107
2108 void
2109 StorageManager::addToDomainList(SecPreferencesDomain domain,
2110 const char* dbName, const CSSM_GUID &guid, uint32 subServiceType)
2111 {
2112 StLock<Mutex>_(mMutex);
2113
2114 if (domain == kSecPreferencesDomainDynamic)
2115 MacOSError::throwMe(errSecInvalidPrefsDomain);
2116
2117 // make the identifier
2118 CSSM_VERSION version = {0, 0};
2119 DLDbIdentifier id = DLDbListCFPref::makeDLDbIdentifier (guid, version, 0,
2120 subServiceType, dbName, NULL);
2121
2122 if (domain == mDomain)
2123 {
2124 // manipulate the user's list
2125 {
2126 mSavedList.revert(true);
2127 mSavedList.add(demungeDLDbIdentifier(id));
2128 mSavedList.save();
2129 }
2130
2131 KCEventNotifier::PostKeychainEvent(kSecKeychainListChangedEvent);
2132 }
2133 else
2134 {
2135 // manipulate the other list
2136 DLDbListCFPref(domain).add(id);
2137 }
2138 }
2139
2140 void
2141 StorageManager::isInDomainList(SecPreferencesDomain domain,
2142 const char* dbName, const CSSM_GUID &guid, uint32 subServiceType)
2143 {
2144 StLock<Mutex>_(mMutex);
2145
2146 if (domain == kSecPreferencesDomainDynamic)
2147 MacOSError::throwMe(errSecInvalidPrefsDomain);
2148
2149 CSSM_VERSION version = {0, 0};
2150 DLDbIdentifier id = DLDbListCFPref::makeDLDbIdentifier (guid, version, 0,
2151 subServiceType, dbName, NULL);
2152
2153 // determine the list to search
2154 bool result;
2155 if (domain == mDomain)
2156 {
2157 result = mSavedList.member(demungeDLDbIdentifier(id));
2158 }
2159 else
2160 {
2161 result = DLDbListCFPref(domain).member(demungeDLDbIdentifier(id));
2162 }
2163
2164 // do the search
2165 if (!result)
2166 {
2167 MacOSError::throwMe(errSecNoSuchKeychain);
2168 }
2169 }
2170
2171 void
2172 StorageManager::removeFromDomainList(SecPreferencesDomain domain,
2173 const char* dbName, const CSSM_GUID &guid, uint32 subServiceType)
2174 {
2175 StLock<Mutex>_(mMutex);
2176
2177 if (domain == kSecPreferencesDomainDynamic)
2178 MacOSError::throwMe(errSecInvalidPrefsDomain);
2179
2180 // make the identifier
2181 CSSM_VERSION version = {0, 0};
2182 DLDbIdentifier id = DLDbListCFPref::makeDLDbIdentifier (guid, version, 0,
2183 subServiceType, dbName, NULL);
2184
2185 if (domain == mDomain)
2186 {
2187 // manipulate the user's list
2188 {
2189 mSavedList.revert(true);
2190 mSavedList.remove(demungeDLDbIdentifier(id));
2191 mSavedList.save();
2192 }
2193
2194 KCEventNotifier::PostKeychainEvent(kSecKeychainListChangedEvent);
2195 }
2196 else
2197 {
2198 // manipulate the other list
2199 DLDbListCFPref(domain).remove(id);
2200 }
2201 }
2202
2203 bool
2204 StorageManager::keychainOwnerPermissionsValidForDomain(const char* path, SecPreferencesDomain domain)
2205 {
2206 struct stat sb;
2207 mode_t perms;
2208 const char* sysPrefDir = "/Library/Preferences";
2209 const char* errMsg = "Will not set default";
2210 char* mustOwnDir = NULL;
2211 struct passwd* pw = NULL;
2212
2213 // get my uid
2214 uid_t uid = geteuid();
2215 if (!uid) uid = getuid();
2216
2217 // our (e)uid must own the appropriate preferences or home directory
2218 // for the specified preference domain whose default we will be modifying
2219 switch (domain) {
2220 case kSecPreferencesDomainUser:
2221 mustOwnDir = getenv("HOME");
2222 if (mustOwnDir == NULL) {
2223 pw = getpwuid(uid);
2224 if (!pw) return false;
2225 mustOwnDir = pw->pw_dir;
2226 }
2227 break;
2228 case kSecPreferencesDomainSystem:
2229 mustOwnDir = (char*)sysPrefDir;
2230 break;
2231 case kSecPreferencesDomainCommon:
2232 mustOwnDir = (char*)sysPrefDir;
2233 break;
2234 default:
2235 return false;
2236 }
2237
2238 if (mustOwnDir != NULL) {
2239 struct stat dsb;
2240 if ( (stat(mustOwnDir, &dsb) != 0) || (dsb.st_uid != uid) ) {
2241 fprintf(stderr, "%s: UID=%d does not own directory %s\n", errMsg, (int)uid, mustOwnDir);
2242 mustOwnDir = NULL; // will return below after calling endpwent()
2243 }
2244 }
2245
2246 if (pw != NULL)
2247 endpwent();
2248
2249 if (mustOwnDir == NULL)
2250 return false;
2251
2252 // check that file actually exists
2253 if (stat(path, &sb) != 0) {
2254 fprintf(stderr, "%s: file %s does not exist\n", errMsg, path);
2255 return false;
2256 }
2257
2258 // check flags
2259 if (sb.st_flags & (SF_IMMUTABLE | UF_IMMUTABLE)) {
2260 fprintf(stderr, "%s: file %s is immutable\n", errMsg, path);
2261 return false;
2262 }
2263
2264 // check ownership
2265 if (sb.st_uid != uid) {
2266 fprintf(stderr, "%s: file %s is owned by UID=%d, but we have UID=%d\n",
2267 errMsg, path, (int)sb.st_uid, (int)uid);
2268 return false;
2269 }
2270
2271 // check mode
2272 perms = sb.st_mode;
2273 perms |= 0600; // must have owner read/write permission set
2274 if (sb.st_mode != perms) {
2275 fprintf(stderr, "%s: file %s does not have the expected permissions\n", errMsg, path);
2276 return false;
2277 }
2278
2279 // user owns file and can read/write it
2280 return true;
2281 }