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