]> git.saurik.com Git - apple/security.git/blob - OSX/libsecurity_keychain/lib/Keychains.cpp
Security-57337.50.23.tar.gz
[apple/security.git] / OSX / libsecurity_keychain / lib / Keychains.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 // Keychains.cpp
27 //
28
29 #include "KCEventNotifier.h"
30 #include "Keychains.h"
31
32 #include "Item.h"
33 #include "KCCursor.h"
34 #include "Globals.h"
35 #include <security_cdsa_utilities/Schema.h>
36 #include <security_cdsa_client/keychainacl.h>
37 #include <security_cdsa_utilities/cssmacl.h>
38 #include <security_cdsa_utilities/cssmdb.h>
39 #include <security_utilities/trackingallocator.h>
40 #include <security_keychain/SecCFTypes.h>
41 #include <securityd_client/ssblob.h>
42 #include <Security/TrustSettingsSchema.h>
43
44 #include "SecKeychainPriv.h"
45
46 #include <Security/SecKeychainItemPriv.h>
47 #include <CoreFoundation/CoreFoundation.h>
48 #include "DLDbListCFPref.h"
49 #include <fcntl.h>
50 #include <sys/param.h>
51 #include <syslog.h>
52 #include <sys/stat.h>
53 #include <sys/socket.h>
54 #include <sys/un.h>
55 #include <sys/types.h>
56 #include <sys/time.h>
57
58 static dispatch_once_t SecKeychainSystemKeychainChecked;
59
60 OSStatus SecKeychainSystemKeychainCheckWouldDeadlock()
61 {
62 dispatch_once(&SecKeychainSystemKeychainChecked, ^{});
63 return errSecSuccess;
64 }
65
66 using namespace KeychainCore;
67 using namespace CssmClient;
68
69
70 typedef struct EventItem
71 {
72 SecKeychainEvent kcEvent;
73 Item item;
74 } EventItem;
75
76 typedef std::list<EventItem> EventBufferSuper;
77 class EventBuffer : public EventBufferSuper
78 {
79 public:
80 EventBuffer () {}
81 virtual ~EventBuffer ();
82 };
83
84
85 EventBuffer::~EventBuffer ()
86 {
87 }
88
89
90
91 //
92 // KeychainSchemaImpl
93 //
94 KeychainSchemaImpl::KeychainSchemaImpl(const Db &db) : mMutex(Mutex::recursive)
95 {
96 DbCursor relations(db);
97 relations->recordType(CSSM_DL_DB_SCHEMA_INFO);
98 DbAttributes relationRecord(db, 1);
99 relationRecord.add(Schema::RelationID);
100 DbUniqueRecord outerUniqueId(db);
101
102 while (relations->next(&relationRecord, NULL, outerUniqueId))
103 {
104 DbUniqueRecord uniqueId(db);
105
106 uint32 relationID = relationRecord.at(0);
107 if (CSSM_DB_RECORDTYPE_SCHEMA_START <= relationID
108 && relationID < CSSM_DB_RECORDTYPE_SCHEMA_END)
109 continue;
110
111 // Create a cursor on the SCHEMA_ATTRIBUTES table for records with
112 // RelationID == relationID
113 DbCursor attributes(db);
114 attributes->recordType(CSSM_DL_DB_SCHEMA_ATTRIBUTES);
115 attributes->add(CSSM_DB_EQUAL, Schema::RelationID, relationID);
116
117 // Set up a record for retriving the SCHEMA_ATTRIBUTES
118 DbAttributes attributeRecord(db, 2);
119 attributeRecord.add(Schema::AttributeFormat);
120 attributeRecord.add(Schema::AttributeID);
121
122 RelationInfoMap &rim = mDatabaseInfoMap[relationID];
123 while (attributes->next(&attributeRecord, NULL, uniqueId))
124 rim[attributeRecord.at(1)] = attributeRecord.at(0);
125
126 // Create a cursor on the CSSM_DL_DB_SCHEMA_INDEXES table for records
127 // with RelationID == relationID
128 DbCursor indexes(db);
129 indexes->recordType(CSSM_DL_DB_SCHEMA_INDEXES);
130 indexes->conjunctive(CSSM_DB_AND);
131 indexes->add(CSSM_DB_EQUAL, Schema::RelationID, relationID);
132 indexes->add(CSSM_DB_EQUAL, Schema::IndexType,
133 uint32(CSSM_DB_INDEX_UNIQUE));
134
135 // Set up a record for retriving the SCHEMA_INDEXES
136 DbAttributes indexRecord(db, 1);
137 indexRecord.add(Schema::AttributeID);
138
139 CssmAutoDbRecordAttributeInfo &infos =
140 *new CssmAutoDbRecordAttributeInfo();
141 mPrimaryKeyInfoMap.
142 insert(PrimaryKeyInfoMap::value_type(relationID, &infos));
143 infos.DataRecordType = relationID;
144 while (indexes->next(&indexRecord, NULL, uniqueId))
145 {
146 CssmDbAttributeInfo &info = infos.add();
147 info.AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_INTEGER;
148 info.Label.AttributeID = indexRecord.at(0);
149 // @@@ Might insert bogus value if DB is corrupt
150 info.AttributeFormat = rim[info.Label.AttributeID];
151 }
152 }
153 }
154
155 KeychainSchemaImpl::~KeychainSchemaImpl()
156 {
157 try
158 {
159 map<CSSM_DB_RECORDTYPE, CssmAutoDbRecordAttributeInfo *>::iterator it = mPrimaryKeyInfoMap.begin();
160 while (it != mPrimaryKeyInfoMap.end())
161 {
162 delete it->second;
163 it++;
164 }
165 // for_each_map_delete(mPrimaryKeyInfoMap.begin(), mPrimaryKeyInfoMap.end());
166 }
167 catch(...)
168 {
169 }
170 }
171
172 const KeychainSchemaImpl::RelationInfoMap &
173 KeychainSchemaImpl::relationInfoMapFor(CSSM_DB_RECORDTYPE recordType) const
174 {
175 DatabaseInfoMap::const_iterator dit = mDatabaseInfoMap.find(recordType);
176 if (dit == mDatabaseInfoMap.end())
177 MacOSError::throwMe(errSecNoSuchClass);
178 return dit->second;
179 }
180
181 bool KeychainSchemaImpl::hasRecordType (CSSM_DB_RECORDTYPE recordType) const
182 {
183 DatabaseInfoMap::const_iterator it = mDatabaseInfoMap.find(recordType);
184 return it != mDatabaseInfoMap.end();
185 }
186
187 bool
188 KeychainSchemaImpl::hasAttribute(CSSM_DB_RECORDTYPE recordType, uint32 attributeId) const
189 {
190 try
191 {
192 const RelationInfoMap &rmap = relationInfoMapFor(recordType);
193 RelationInfoMap::const_iterator rit = rmap.find(attributeId);
194 return rit != rmap.end();
195 }
196 catch (MacOSError result)
197 {
198 if (result.osStatus () == errSecNoSuchClass)
199 {
200 return false;
201 }
202 else
203 {
204 throw;
205 }
206 }
207 }
208
209 CSSM_DB_ATTRIBUTE_FORMAT
210 KeychainSchemaImpl::attributeFormatFor(CSSM_DB_RECORDTYPE recordType, uint32 attributeId) const
211 {
212 const RelationInfoMap &rmap = relationInfoMapFor(recordType);
213 RelationInfoMap::const_iterator rit = rmap.find(attributeId);
214 if (rit == rmap.end())
215 MacOSError::throwMe(errSecNoSuchAttr);
216
217 return rit->second;
218 }
219
220 CssmDbAttributeInfo
221 KeychainSchemaImpl::attributeInfoFor(CSSM_DB_RECORDTYPE recordType, uint32 attributeId) const
222 {
223 CSSM_DB_ATTRIBUTE_INFO info;
224 info.AttributeFormat = attributeFormatFor(recordType, attributeId);
225 info.AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_INTEGER;
226 info.Label.AttributeID = attributeId;
227
228 return info;
229 }
230
231 void
232 KeychainSchemaImpl::getAttributeInfoForRecordType(CSSM_DB_RECORDTYPE recordType, SecKeychainAttributeInfo **Info) const
233 {
234 const RelationInfoMap &rmap = relationInfoMapFor(recordType);
235
236 SecKeychainAttributeInfo *theList=reinterpret_cast<SecKeychainAttributeInfo *>(malloc(sizeof(SecKeychainAttributeInfo)));
237
238 size_t capacity=rmap.size();
239 UInt32 *tagBuf=reinterpret_cast<UInt32 *>(malloc(capacity*sizeof(UInt32)));
240 UInt32 *formatBuf=reinterpret_cast<UInt32 *>(malloc(capacity*sizeof(UInt32)));
241 UInt32 i=0;
242
243
244 for (RelationInfoMap::const_iterator rit = rmap.begin(); rit != rmap.end(); ++rit)
245 {
246 if (i>=capacity)
247 {
248 capacity *= 2;
249 if (capacity <= i) capacity = i + 1;
250 tagBuf=reinterpret_cast<UInt32 *>(realloc(tagBuf, (capacity*sizeof(UInt32))));
251 formatBuf=reinterpret_cast<UInt32 *>(realloc(tagBuf, (capacity*sizeof(UInt32))));
252 }
253 tagBuf[i]=rit->first;
254 formatBuf[i++]=rit->second;
255 }
256
257 theList->count=i;
258 theList->tag=tagBuf;
259 theList->format=formatBuf;
260 *Info=theList;
261 }
262
263
264 const CssmAutoDbRecordAttributeInfo &
265 KeychainSchemaImpl::primaryKeyInfosFor(CSSM_DB_RECORDTYPE recordType) const
266 {
267 PrimaryKeyInfoMap::const_iterator it;
268 it = mPrimaryKeyInfoMap.find(recordType);
269
270 if (it == mPrimaryKeyInfoMap.end())
271 MacOSError::throwMe(errSecNoSuchClass); // @@@ Not really but whatever.
272
273 return *it->second;
274 }
275
276 bool
277 KeychainSchemaImpl::operator <(const KeychainSchemaImpl &other) const
278 {
279 return mDatabaseInfoMap < other.mDatabaseInfoMap;
280 }
281
282 bool
283 KeychainSchemaImpl::operator ==(const KeychainSchemaImpl &other) const
284 {
285 return mDatabaseInfoMap == other.mDatabaseInfoMap;
286 }
287
288 void
289 KeychainSchemaImpl::didCreateRelation(CSSM_DB_RECORDTYPE relationID,
290 const char *inRelationName,
291 uint32 inNumberOfAttributes,
292 const CSSM_DB_SCHEMA_ATTRIBUTE_INFO *pAttributeInfo,
293 uint32 inNumberOfIndexes,
294 const CSSM_DB_SCHEMA_INDEX_INFO *pIndexInfo)
295 {
296 StLock<Mutex>_(mMutex);
297
298 if (CSSM_DB_RECORDTYPE_SCHEMA_START <= relationID
299 && relationID < CSSM_DB_RECORDTYPE_SCHEMA_END)
300 return;
301
302 // if our schema is already in the map, return
303 if (mPrimaryKeyInfoMap.find(relationID) != mPrimaryKeyInfoMap.end())
304 {
305 return;
306 }
307
308 RelationInfoMap &rim = mDatabaseInfoMap[relationID];
309 for (uint32 ix = 0; ix < inNumberOfAttributes; ++ix)
310 rim[pAttributeInfo[ix].AttributeId] = pAttributeInfo[ix].DataType;
311
312 CssmAutoDbRecordAttributeInfo *infos = new CssmAutoDbRecordAttributeInfo();
313
314 mPrimaryKeyInfoMap.
315 insert(PrimaryKeyInfoMap::value_type(relationID, infos));
316 infos->DataRecordType = relationID;
317 for (uint32 ix = 0; ix < inNumberOfIndexes; ++ix)
318 if (pIndexInfo[ix].IndexType == CSSM_DB_INDEX_UNIQUE)
319 {
320 CssmDbAttributeInfo &info = infos->add();
321 info.AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_INTEGER;
322 info.Label.AttributeID = pIndexInfo[ix].AttributeId;
323 info.AttributeFormat = rim[info.Label.AttributeID];
324 }
325 }
326
327
328
329 KeychainSchema::~KeychainSchema()
330
331 {
332 }
333
334
335
336 struct Event
337 {
338 SecKeychainEvent eventCode;
339 PrimaryKey primaryKey;
340 };
341 typedef std::list<Event> EventList;
342
343 #define SYSTEM_KEYCHAIN_CHECK_UNIX_BASE_NAME "/var/run/systemkeychaincheck"
344 #define SYSTEM_KEYCHAIN_CHECK_UNIX_DOMAIN_SOCKET_NAME (SYSTEM_KEYCHAIN_CHECK_UNIX_BASE_NAME ".socket")
345 #define SYSTEM_KEYCHAIN_CHECK_COMPLETE_FILE_NAME (SYSTEM_KEYCHAIN_CHECK_UNIX_BASE_NAME ".done")
346
347 static void check_system_keychain()
348 {
349 // sadly we can't use XPC here, XPC_DOMAIN_TYPE_SYSTEM doesn't exist yet. Also xpc-helper uses the
350 // keychain API (I assume for checking codesign things). So we use Unix Domain Sockets.
351
352 // NOTE: if we hit a system error we attempt to log it, and then just don't check the system keychain.
353 // In theory a system might be able to recover from this state if we let it try to muddle along, and
354 // past behaviour didn't even try this hard to do the keychain check. In particular we could be in a
355 // sandbox'ed process. So we just do our best and let another process try again.
356
357 struct stat keycheck_file_info;
358 if (stat(SYSTEM_KEYCHAIN_CHECK_COMPLETE_FILE_NAME, &keycheck_file_info) < 0) {
359 int server_fd = socket(PF_UNIX, SOCK_STREAM, 0);
360 if (server_fd < 0) {
361 syslog(LOG_ERR, "Can't get socket (%m) system keychain may be unchecked");
362 return;
363 }
364
365 struct sockaddr_un keychain_check_server_address;
366 keychain_check_server_address.sun_family = AF_UNIX;
367 if (strlcpy(keychain_check_server_address.sun_path, SYSTEM_KEYCHAIN_CHECK_UNIX_DOMAIN_SOCKET_NAME, sizeof(keychain_check_server_address.sun_path)) > sizeof(keychain_check_server_address.sun_path)) {
368 // It would be nice if we could compile time assert this
369 syslog(LOG_ERR, "Socket path too long, max length %lu, your length %lu", (unsigned long)sizeof(keychain_check_server_address.sun_path), (unsigned long)strlen(SYSTEM_KEYCHAIN_CHECK_UNIX_DOMAIN_SOCKET_NAME));
370 close(server_fd);
371 return;
372 }
373 keychain_check_server_address.sun_len = SUN_LEN(&keychain_check_server_address);
374
375 int rc = connect(server_fd, (struct sockaddr *)&keychain_check_server_address, keychain_check_server_address.sun_len);
376 if (rc < 0) {
377 syslog(LOG_ERR, "Can not connect to %s: %m", SYSTEM_KEYCHAIN_CHECK_UNIX_DOMAIN_SOCKET_NAME);
378 close(server_fd);
379 return;
380 }
381
382 // this read lets us block until the EOF comes, we don't ever get a byte (and if we do, we don't care about it)
383 char byte;
384 ssize_t read_size = read(server_fd, &byte, 1);
385 if (read_size < 0) {
386 syslog(LOG_ERR, "Error reading from system keychain checker: %m");
387 }
388
389 close(server_fd);
390 return;
391 }
392 }
393
394 //
395 // KeychainImpl
396 //
397 KeychainImpl::KeychainImpl(const Db &db)
398 : mAttemptedUpgrade(false), mDbItemMapMutex(Mutex::recursive), mDbDeletedItemMapMutex(Mutex::recursive),
399 mInCache(false), mDb(db), mCustomUnlockCreds (this), mIsInBatchMode (false), mMutex(Mutex::recursive)
400 {
401 dispatch_once(&SecKeychainSystemKeychainChecked, ^{
402 check_system_keychain();
403 });
404 mDb->defaultCredentials(this); // install activation hook
405 mEventBuffer = new EventBuffer;
406 }
407
408 KeychainImpl::~KeychainImpl()
409 {
410 try
411 {
412 // Remove ourselves from the cache if we are in it.
413 // fprintf(stderr, "Removing %p from storage manager cache.\n", handle(false));
414 globals().storageManager.removeKeychain(dlDbIdentifier(), this);
415 delete mEventBuffer;
416 }
417 catch(...)
418 {
419 }
420 }
421
422 Mutex*
423 KeychainImpl::getMutexForObject()
424 {
425 return globals().storageManager.getStorageManagerMutex();
426 }
427
428 Mutex*
429 KeychainImpl::getKeychainMutex()
430 {
431 return &mMutex;
432 }
433
434 ReadWriteLock*
435 KeychainImpl::getKeychainReadWriteLock()
436 {
437 return &mRWLock;
438 }
439
440 void KeychainImpl::aboutToDestruct()
441 {
442 // remove me from the global cache, we are done
443 // fprintf(stderr, "Destructing keychain object\n");
444 DLDbIdentifier identifier = dlDbIdentifier();
445 globals().storageManager.removeKeychain(identifier, this);
446 }
447
448 bool
449 KeychainImpl::operator ==(const KeychainImpl &keychain) const
450 {
451 return dlDbIdentifier() == keychain.dlDbIdentifier();
452 }
453
454 KCCursor
455 KeychainImpl::createCursor(SecItemClass itemClass, const SecKeychainAttributeList *attrList)
456 {
457 StLock<Mutex>_(mMutex);
458
459 StorageManager::KeychainList keychains;
460 keychains.push_back(Keychain(this));
461 return KCCursor(keychains, itemClass, attrList);
462 }
463
464 KCCursor
465 KeychainImpl::createCursor(const SecKeychainAttributeList *attrList)
466 {
467 StLock<Mutex>_(mMutex);
468
469 StorageManager::KeychainList keychains;
470 keychains.push_back(Keychain(this));
471 return KCCursor(keychains, attrList);
472 }
473
474 void
475 KeychainImpl::create(UInt32 passwordLength, const void *inPassword)
476 {
477 StLock<Mutex>_(mMutex);
478
479 if (!inPassword)
480 {
481 create();
482 return;
483 }
484
485 Allocator &alloc = Allocator::standard();
486
487 // @@@ Share this instance
488
489 const CssmData password(const_cast<void *>(inPassword), passwordLength);
490 AclFactory::PasswordChangeCredentials pCreds (password, alloc);
491 AclFactory::AnyResourceContext rcc(pCreds);
492 create(&rcc);
493 }
494
495 void KeychainImpl::create(ConstStringPtr inPassword)
496 {
497 StLock<Mutex>_(mMutex);
498
499 if ( inPassword )
500 create(static_cast<UInt32>(inPassword[0]), &inPassword[1]);
501 else
502 create();
503 }
504
505 void
506 KeychainImpl::create()
507 {
508 StLock<Mutex>_(mMutex);
509
510 AclFactory aclFactory;
511 AclFactory::AnyResourceContext rcc(aclFactory.unlockCred());
512 create(&rcc);
513 }
514
515 void KeychainImpl::createWithBlob(CssmData &blob)
516 {
517 StLock<Mutex>_(mMutex);
518
519 mDb->dbInfo(&Schema::DBInfo);
520 AclFactory aclFactory;
521 AclFactory::AnyResourceContext rcc(aclFactory.unlockCred());
522 mDb->resourceControlContext (&rcc);
523 try
524 {
525 mDb->createWithBlob(blob);
526 }
527 catch (...)
528 {
529 mDb->resourceControlContext(NULL);
530 mDb->dbInfo(NULL);
531 throw;
532 }
533 mDb->resourceControlContext(NULL);
534 mDb->dbInfo(NULL); // Clear the schema (to not break an open call later)
535 globals().storageManager.created(Keychain(this));
536
537 KCEventNotifier::PostKeychainEvent (kSecKeychainListChangedEvent, this, NULL);
538 }
539
540 void
541 KeychainImpl::create(const ResourceControlContext *rcc)
542 {
543 StLock<Mutex>_(mMutex);
544
545 mDb->dbInfo(&Schema::DBInfo); // Set the schema (to force a create)
546 mDb->resourceControlContext(rcc);
547 try
548 {
549 mDb->create();
550 }
551 catch (...)
552 {
553 mDb->resourceControlContext(NULL);
554 mDb->dbInfo(NULL); // Clear the schema (to not break an open call later)
555 throw;
556 }
557 mDb->resourceControlContext(NULL);
558 mDb->dbInfo(NULL); // Clear the schema (to not break an open call later)
559 globals().storageManager.created(Keychain(this));
560 }
561
562 void
563 KeychainImpl::open()
564 {
565 StLock<Mutex>_(mMutex);
566
567 mDb->open();
568 }
569
570 void
571 KeychainImpl::lock()
572 {
573 StLock<Mutex>_(mMutex);
574
575 mDb->lock();
576 }
577
578 void
579 KeychainImpl::unlock()
580 {
581 StLock<Mutex>_(mMutex);
582
583 mDb->unlock();
584 }
585
586 void
587 KeychainImpl::unlock(const CssmData &password)
588 {
589 StLock<Mutex>_(mMutex);
590
591 mDb->unlock(password);
592 }
593
594 void
595 KeychainImpl::unlock(ConstStringPtr password)
596 {
597 StLock<Mutex>_(mMutex);
598
599 if (password)
600 {
601 const CssmData data(const_cast<unsigned char *>(&password[1]), password[0]);
602 unlock(data);
603 }
604 else
605 unlock();
606 }
607
608 void
609 KeychainImpl::stash()
610 {
611 StLock<Mutex>_(mMutex);
612
613 mDb->stash();
614 }
615
616 void
617 KeychainImpl::stashCheck()
618 {
619 StLock<Mutex>_(mMutex);
620
621 mDb->stashCheck();
622 }
623
624 void
625 KeychainImpl::getSettings(uint32 &outIdleTimeOut, bool &outLockOnSleep)
626 {
627 StLock<Mutex>_(mMutex);
628
629 mDb->getSettings(outIdleTimeOut, outLockOnSleep);
630 }
631
632 void
633 KeychainImpl::setSettings(uint32 inIdleTimeOut, bool inLockOnSleep)
634 {
635 StLock<Mutex>_(mMutex);
636
637 // The .Mac syncing code only makes sense for the AppleFile CSP/DL,
638 // but other DLs such as the OCSP and LDAP DLs do not expose a way to
639 // change settings or the password. To make a minimal change that only affects
640 // the smartcard case, we only look for that CSP/DL
641
642 bool isSmartcard = (mDb->dl()->guid() == gGuidAppleSdCSPDL);
643
644 // get the old keychain blob so that we can tell .Mac to resync it
645 CssmAutoData oldBlob(mDb ->allocator());
646 if (!isSmartcard)
647 mDb->copyBlob(oldBlob.get());
648
649 mDb->setSettings(inIdleTimeOut, inLockOnSleep);
650 }
651
652 void
653 KeychainImpl::changePassphrase(UInt32 oldPasswordLength, const void *oldPassword,
654 UInt32 newPasswordLength, const void *newPassword)
655 {
656 StLock<Mutex>_(mMutex);
657
658 bool isSmartcard = (mDb->dl()->guid() == gGuidAppleSdCSPDL);
659
660 TrackingAllocator allocator(Allocator::standard());
661 AutoCredentials cred = AutoCredentials(allocator);
662 if (oldPassword)
663 {
664 const CssmData &oldPass = *new(allocator) CssmData(const_cast<void *>(oldPassword), oldPasswordLength);
665 TypedList &oldList = *new(allocator) TypedList(allocator, CSSM_SAMPLE_TYPE_KEYCHAIN_LOCK);
666 oldList.append(new(allocator) ListElement(CSSM_SAMPLE_TYPE_PASSWORD));
667 oldList.append(new(allocator) ListElement(oldPass));
668 cred += oldList;
669 }
670
671 if (newPassword)
672 {
673 const CssmData &newPass = *new(allocator) CssmData(const_cast<void *>(newPassword), newPasswordLength);
674 TypedList &newList = *new(allocator) TypedList(allocator, CSSM_SAMPLE_TYPE_KEYCHAIN_CHANGE_LOCK);
675 newList.append(new(allocator) ListElement(CSSM_SAMPLE_TYPE_PASSWORD));
676 newList.append(new(allocator) ListElement(newPass));
677 cred += newList;
678 }
679
680 // get the old keychain blob so that we can tell .Mac to resync it
681 CssmAutoData oldBlob(mDb->allocator());
682 if (!isSmartcard)
683 mDb->copyBlob(oldBlob.get());
684
685 mDb->changePassphrase(&cred);
686 }
687
688 void
689 KeychainImpl::changePassphrase(ConstStringPtr oldPassword, ConstStringPtr newPassword)
690 {
691 StLock<Mutex>_(mMutex);
692
693 const void *oldPtr, *newPtr;
694 UInt32 oldLen, newLen;
695 if (oldPassword)
696 {
697 oldLen = oldPassword[0];
698 oldPtr = oldPassword + 1;
699 }
700 else
701 {
702 oldLen = 0;
703 oldPtr = NULL;
704 }
705
706 if (newPassword)
707 {
708 newLen = newPassword[0];
709 newPtr = newPassword + 1;
710 }
711 else
712 {
713 newLen = 0;
714 newPtr = NULL;
715 }
716
717 changePassphrase(oldLen, oldPtr, newLen, newPtr);
718 }
719
720 void
721 KeychainImpl::authenticate(const CSSM_ACCESS_CREDENTIALS *cred)
722 {
723 StLock<Mutex>_(mMutex);
724
725 if (!exists())
726 MacOSError::throwMe(errSecNoSuchKeychain);
727
728 MacOSError::throwMe(errSecUnimplemented);
729 }
730
731 UInt32
732 KeychainImpl::status() const
733 {
734 // @@@ We should figure out the read/write status though a DL passthrough
735 // or some other way. Also should locked be unlocked read only or just
736 // read-only?
737 return (mDb->isLocked() ? 0 : kSecUnlockStateStatus | kSecWritePermStatus)
738 | kSecReadPermStatus;
739 }
740
741 bool
742 KeychainImpl::exists()
743 {
744 StLock<Mutex>_(mMutex);
745
746 bool exists = true;
747 try
748 {
749 open();
750 // Ok to leave the mDb open since it will get closed when it goes away.
751 }
752 catch (const CssmError &e)
753 {
754 if (e.osStatus() != CSSMERR_DL_DATASTORE_DOESNOT_EXIST)
755 throw;
756 exists = false;
757 }
758
759 return exists;
760 }
761
762 bool
763 KeychainImpl::isActive() const
764 {
765 return mDb->isActive();
766 }
767
768 void KeychainImpl::completeAdd(Item &inItem, PrimaryKey &primaryKey)
769 {
770
771
772 // The inItem shouldn't be in the cache yet
773 assert(!inItem->inCache());
774
775 // Insert inItem into mDbItemMap with key primaryKey. p.second will be
776 // true if it got inserted. If not p.second will be false and p.first
777 // will point to the current entry with key primaryKey.
778 StLock<Mutex> _(mDbItemMapMutex);
779 pair<DbItemMap::iterator, bool> p =
780 mDbItemMap.insert(DbItemMap::value_type(primaryKey, inItem.get()));
781 if (!p.second)
782 {
783 // There was already an ItemImpl * in mDbItemMap with key
784 // primaryKey. Remove it, and try the add again.
785 ItemImpl *oldItem = p.first->second;
786
787 // @@@ If this happens we are breaking our API contract of
788 // uniquifying items. We really need to insert the item into the
789 // map before we start the add. And have the item be in an
790 // "is being added" state.
791 secdebug("keychain", "add of new item %p somehow replaced %p",
792 inItem.get(), oldItem);
793
794 mDbItemMap.erase(p.first);
795 oldItem->inCache(false);
796 forceRemoveFromCache(oldItem);
797 mDbItemMap.insert(DbItemMap::value_type(primaryKey, inItem.get()));
798 }
799
800 inItem->inCache(true);
801 }
802
803 void
804 KeychainImpl::addCopy(Item &inItem)
805 {
806 Keychain keychain(this);
807 PrimaryKey primaryKey = inItem->addWithCopyInfo(keychain, true);
808 completeAdd(inItem, primaryKey);
809 postEvent(kSecAddEvent, inItem);
810 }
811
812 void
813 KeychainImpl::add(Item &inItem)
814 {
815 Keychain keychain(this);
816 PrimaryKey primaryKey = inItem->add(keychain);
817 completeAdd(inItem, primaryKey);
818 postEvent(kSecAddEvent, inItem);
819 }
820
821 void
822 KeychainImpl::didUpdate(const Item &inItem, PrimaryKey &oldPK,
823 PrimaryKey &newPK)
824 {
825 // If the primary key hasn't changed we don't need to update mDbItemMap.
826 if (oldPK != newPK)
827 {
828 // If inItem isn't in the cache we don't need to update mDbItemMap.
829 assert(inItem->inCache());
830 if (inItem->inCache())
831 {
832 StLock<Mutex> _(mDbItemMapMutex);
833 // First remove the entry for inItem in mDbItemMap with key oldPK.
834 DbItemMap::iterator it = mDbItemMap.find(oldPK);
835 if (it != mDbItemMap.end() && (ItemImpl*) it->second == inItem.get())
836 mDbItemMap.erase(it);
837
838 // Insert inItem into mDbItemMap with key newPK. p.second will be
839 // true if it got inserted. If not p.second will be false and
840 // p.first will point to the current entry with key newPK.
841 pair<DbItemMap::iterator, bool> p =
842 mDbItemMap.insert(DbItemMap::value_type(newPK, inItem.get()));
843 if (!p.second)
844 {
845 // There was already an ItemImpl * in mDbItemMap with key
846 // primaryKey. Remove it, and try the add again.
847 ItemImpl *oldItem = p.first->second;
848
849 // @@@ If this happens we are breaking our API contract of
850 // uniquifying items. We really need to insert the item into
851 // the map with the new primary key before we start the update.
852 // And have the item be in an "is being updated" state.
853 secdebug("keychain", "update of item %p somehow replaced %p",
854 inItem.get(), oldItem);
855
856 mDbItemMap.erase(p.first);
857 oldItem->inCache(false);
858 forceRemoveFromCache(oldItem);
859 mDbItemMap.insert(DbItemMap::value_type(newPK, inItem.get()));
860 }
861 }
862 }
863
864 // Item updates now are technically a delete and re-add, so post these events instead of kSecUpdateEvent
865 postEvent(kSecDeleteEvent, inItem);
866 postEvent(kSecAddEvent, inItem);
867 }
868
869 void
870 KeychainImpl::deleteItem(Item &inoutItem)
871 {
872 {
873 // We don't need to hold the DO mutex through event posting, and, in fact, doing so causes deadlock.
874 // Hold it only as long as needed, instead.
875
876
877 // item must be persistent
878 if (!inoutItem->isPersistent())
879 MacOSError::throwMe(errSecInvalidItemRef);
880
881 secdebug("kcnotify", "starting deletion of item %p", inoutItem.get());
882
883 DbUniqueRecord uniqueId = inoutItem->dbUniqueRecord();
884 PrimaryKey primaryKey = inoutItem->primaryKey();
885 uniqueId->deleteRecord();
886
887 // Move the item from mDbItemMap to mDbDeletedItemMap. We need the item
888 // to give to the client process when we receive the kSecDeleteEvent
889 // notification, but if that notification never arrives, we don't want
890 // the item hanging around. When didDeleteItem is called by CCallbackMgr,
891 // we'll remove all traces of the item.
892
893 if (inoutItem->inCache()) {
894 StLock<Mutex> _(mDbItemMapMutex);
895 StLock<Mutex> __(mDbDeletedItemMapMutex);
896 // Only look for it if it's in the cache
897 DbItemMap::iterator it = mDbItemMap.find(primaryKey);
898
899 if (it != mDbItemMap.end() && (ItemImpl*) it->second == inoutItem.get()) {
900 mDbDeletedItemMap.insert(DbItemMap::value_type(primaryKey, it->second));
901 mDbItemMap.erase(it);
902 }
903 }
904
905 // Post the notification for the item deletion with
906 // the primaryKey obtained when the item still existed
907 }
908
909 postEvent(kSecDeleteEvent, inoutItem);
910 }
911
912
913 CssmClient::CSP
914 KeychainImpl::csp()
915 {
916 StLock<Mutex>_(mMutex);
917
918 if (!mDb->dl()->subserviceMask() & CSSM_SERVICE_CSP)
919 MacOSError::throwMe(errSecInvalidKeychain);
920
921 // Try to cast first to a CSPDL to handle case where we don't have an SSDb
922 try
923 {
924 CssmClient::CSPDL cspdl(dynamic_cast<CssmClient::CSPDLImpl *>(&*mDb->dl()));
925 return CSP(cspdl);
926 }
927 catch (...)
928 {
929 SSDbImpl* impl = dynamic_cast<SSDbImpl *>(&(*mDb));
930 if (impl == NULL)
931 {
932 CssmError::throwMe(CSSMERR_CSSM_INVALID_POINTER);
933 }
934
935 SSDb ssDb(impl);
936 return ssDb->csp();
937 }
938 }
939
940 PrimaryKey
941 KeychainImpl::makePrimaryKey(CSSM_DB_RECORDTYPE recordType, DbUniqueRecord &uniqueId)
942 {
943 StLock<Mutex>_(mMutex);
944
945 DbAttributes primaryKeyAttrs(uniqueId->database());
946 primaryKeyAttrs.recordType(recordType);
947 gatherPrimaryKeyAttributes(primaryKeyAttrs);
948 uniqueId->get(&primaryKeyAttrs, NULL);
949 return PrimaryKey(primaryKeyAttrs);
950 }
951
952 PrimaryKey
953 KeychainImpl::makePrimaryKey(CSSM_DB_RECORDTYPE recordType, DbAttributes* currentAttributes)
954 {
955 StLock<Mutex>_(mMutex);
956
957 DbAttributes primaryKeyAttrs;
958 primaryKeyAttrs.recordType(recordType);
959 gatherPrimaryKeyAttributes(primaryKeyAttrs);
960
961 for(int i = 0; i < primaryKeyAttrs.size(); i++) {
962 CssmDbAttributeData& attr = primaryKeyAttrs[i];
963
964 CssmDbAttributeData * actual = currentAttributes->find(attr.info());
965 if(actual) {
966 attr.set(*actual, Allocator::standard());
967 }
968 }
969 return PrimaryKey(primaryKeyAttrs);
970 }
971
972 const CssmAutoDbRecordAttributeInfo &
973 KeychainImpl::primaryKeyInfosFor(CSSM_DB_RECORDTYPE recordType)
974 {
975 StLock<Mutex>_(mMutex);
976
977 try
978 {
979 return keychainSchema()->primaryKeyInfosFor(recordType);
980 }
981 catch (const CommonError &error)
982 {
983 switch (error.osStatus())
984 {
985 case errSecNoSuchClass:
986 case CSSMERR_DL_INVALID_RECORDTYPE:
987 resetSchema();
988 return keychainSchema()->primaryKeyInfosFor(recordType);
989 default:
990 throw;
991 }
992 }
993 }
994
995 void KeychainImpl::gatherPrimaryKeyAttributes(DbAttributes& primaryKeyAttrs)
996 {
997 StLock<Mutex> _(mMutex);
998
999 const CssmAutoDbRecordAttributeInfo &infos =
1000 primaryKeyInfosFor(primaryKeyAttrs.recordType());
1001
1002 // @@@ fix this to not copy info.
1003 for (uint32 i = 0; i < infos.size(); i++)
1004 primaryKeyAttrs.add(infos.at(i));
1005 }
1006
1007 ItemImpl *
1008 KeychainImpl::_lookupItem(const PrimaryKey &primaryKey)
1009 {
1010 StLock<Mutex> _(mDbItemMapMutex);
1011 DbItemMap::iterator it = mDbItemMap.find(primaryKey);
1012 if (it != mDbItemMap.end())
1013 {
1014 return it->second;
1015 }
1016
1017 return NULL;
1018 }
1019
1020 ItemImpl *
1021 KeychainImpl::_lookupDeletedItemOnly(const PrimaryKey &primaryKey)
1022 {
1023 DbItemMap::iterator it = mDbDeletedItemMap.find(primaryKey);
1024 if (it != mDbDeletedItemMap.end())
1025 {
1026 return it->second;
1027 }
1028
1029 return NULL;
1030 }
1031
1032 Item
1033 KeychainImpl::item(const PrimaryKey &primaryKey)
1034 {
1035 StLock<Mutex>_(mMutex);
1036
1037 // Lookup the item in the map while holding the apiLock.
1038 ItemImpl *itemImpl = _lookupItem(primaryKey);
1039 if (itemImpl) {
1040 return Item(itemImpl);
1041 }
1042
1043 try
1044 {
1045 // We didn't find it so create a new item with just a keychain and
1046 // a primary key. However since we aren't holding
1047 // globals().apiLock anymore some other thread might have beaten
1048 // us to creating this item and adding it to the cache. If that
1049 // happens we retry the lookup.
1050 return Item(this, primaryKey);
1051 }
1052 catch (const MacOSError &e)
1053 {
1054 // If the item creation failed because some other thread already
1055 // inserted this item into the cache we retry the lookup.
1056 if (e.osStatus() == errSecDuplicateItem)
1057 {
1058 // Lookup the item in the map while holding the apiLock.
1059 ItemImpl *itemImpl = _lookupItem(primaryKey);
1060 if (itemImpl)
1061 return Item(itemImpl);
1062 }
1063 throw;
1064 }
1065 }
1066 // Check for an item that may have been deleted.
1067 Item
1068 KeychainImpl::itemdeleted(const PrimaryKey& primaryKey) {
1069 StLock<Mutex>_(mMutex);
1070
1071 Item i = _lookupDeletedItemOnly(primaryKey);
1072 if(i.get()) {
1073 return i;
1074 } else {
1075 return item(primaryKey);
1076 }
1077 }
1078
1079
1080 Item
1081 KeychainImpl::item(CSSM_DB_RECORDTYPE recordType, DbUniqueRecord &uniqueId)
1082 {
1083 StLock<Mutex>_(mMutex);
1084
1085 PrimaryKey primaryKey = makePrimaryKey(recordType, uniqueId);
1086 {
1087 // Lookup the item in the map while holding the apiLock.
1088 ItemImpl *itemImpl = _lookupItem(primaryKey);
1089
1090 if (itemImpl)
1091 {
1092 return Item(itemImpl);
1093 }
1094 }
1095
1096 try
1097 {
1098 // We didn't find it so create a new item with a keychain, a primary key
1099 // and a DbUniqueRecord. However since we aren't holding
1100 // globals().apiLock anymore some other thread might have beaten
1101 // us to creating this item and adding it to the cache. If that
1102 // happens we retry the lookup.
1103 return Item(this, primaryKey, uniqueId);
1104 }
1105 catch (const MacOSError &e)
1106 {
1107 // If the item creation failed because some other thread already
1108 // inserted this item into the cache we retry the lookup.
1109 if (e.osStatus() == errSecDuplicateItem)
1110 {
1111 // Lookup the item in the map while holding the apiLock.
1112 ItemImpl *itemImpl = _lookupItem(primaryKey);
1113 if (itemImpl)
1114 return Item(itemImpl);
1115 }
1116 throw;
1117 }
1118 }
1119
1120 KeychainSchema
1121 KeychainImpl::keychainSchema()
1122 {
1123 StLock<Mutex>_(mMutex);
1124 if (!mKeychainSchema)
1125 mKeychainSchema = KeychainSchema(mDb);
1126
1127 return mKeychainSchema;
1128 }
1129
1130 void KeychainImpl::resetSchema()
1131 {
1132 mKeychainSchema = NULL; // re-fetch it from db next time
1133 }
1134
1135
1136 // Called from DbItemImpl's constructor (so it is only partially constructed),
1137 // add it to the map.
1138 void
1139 KeychainImpl::addItem(const PrimaryKey &primaryKey, ItemImpl *dbItemImpl)
1140 {
1141 StLock<Mutex>_(mMutex);
1142
1143 // The dbItemImpl shouldn't be in the cache yet
1144 assert(!dbItemImpl->inCache());
1145
1146 // Insert dbItemImpl into mDbItemMap with key primaryKey. p.second will
1147 // be true if it got inserted. If not p.second will be false and p.first
1148 // will point to the current entry with key primaryKey.
1149 StLock<Mutex> __(mDbItemMapMutex);
1150 pair<DbItemMap::iterator, bool> p =
1151 mDbItemMap.insert(DbItemMap::value_type(primaryKey, dbItemImpl));
1152
1153 if (!p.second)
1154 {
1155 // There was already an ItemImpl * in mDbItemMap with key primaryKey.
1156 // There is a race condition here when being called in multiple threads
1157 // We might have added an item using add and received a notification at
1158 // the same time.
1159 MacOSError::throwMe(errSecDuplicateItem);
1160 }
1161
1162 dbItemImpl->inCache(true);
1163 }
1164
1165 void
1166 KeychainImpl::didDeleteItem(ItemImpl *inItemImpl)
1167 {
1168 StLock<Mutex>_(mMutex);
1169
1170 // Called by CCallbackMgr
1171 secdebug("kcnotify", "%p notified that item %p was deleted", this, inItemImpl);
1172 removeItem(inItemImpl->primaryKey(), inItemImpl);
1173 }
1174
1175 void
1176 KeychainImpl::removeItem(const PrimaryKey &primaryKey, ItemImpl *inItemImpl)
1177 {
1178 StLock<Mutex>_(mMutex);
1179
1180 // If inItemImpl isn't in the cache to begin with we are done.
1181 if (!inItemImpl->inCache())
1182 return;
1183
1184 {
1185 StLock<Mutex> _(mDbItemMapMutex);
1186 DbItemMap::iterator it = mDbItemMap.find(primaryKey);
1187 if (it != mDbItemMap.end() && (ItemImpl*) it->second == inItemImpl) {
1188 mDbItemMap.erase(it);
1189 }
1190 } // drop mDbItemMapMutex
1191
1192 {
1193 StLock<Mutex> _(mDbDeletedItemMapMutex);
1194 DbItemMap::iterator it = mDbDeletedItemMap.find(primaryKey);
1195 if (it != mDbDeletedItemMap.end() && (ItemImpl*) it->second == inItemImpl) {
1196 mDbDeletedItemMap.erase(it);
1197 }
1198 } // drop mDbDeletedItemMapMutex
1199
1200 inItemImpl->inCache(false);
1201 }
1202
1203 void
1204 KeychainImpl::forceRemoveFromCache(ItemImpl* inItemImpl) {
1205 try {
1206 // Wrap all this in a try-block and ignore all errors - we're trying to clean up these maps
1207 {
1208 StLock<Mutex> _(mDbItemMapMutex);
1209 for(DbItemMap::iterator it = mDbItemMap.begin(); it != mDbItemMap.end(); ) {
1210 if(it->second == inItemImpl) {
1211 // Increment the iterator, but use its pre-increment value for the erase
1212 it->second->inCache(false);
1213 mDbItemMap.erase(it++);
1214 } else {
1215 it++;
1216 }
1217 }
1218 } // drop mDbItemMapMutex
1219
1220 {
1221 StLock<Mutex> _(mDbDeletedItemMapMutex);
1222 for(DbItemMap::iterator it = mDbDeletedItemMap.begin(); it != mDbDeletedItemMap.end(); ) {
1223 if(it->second == inItemImpl) {
1224 // Increment the iterator, but use its pre-increment value for the erase
1225 it->second->inCache(false);
1226 mDbDeletedItemMap.erase(it++);
1227 } else {
1228 it++;
1229 }
1230 }
1231 } // drop mDbDeletedItemMapMutex
1232 } catch(UnixError ue) {
1233 secdebugfunc("keychain", "caught UnixError: %d %s", ue.unixError(), ue.what());
1234 } catch (CssmError cssme) {
1235 const char* errStr = cssmErrorString(cssme.error);
1236 secdebugfunc("keychain", "caught CssmError: %d %s", (int) cssme.error, errStr);
1237 } catch (MacOSError mose) {
1238 secdebugfunc("keychain", "MacOSError: %d", (int)mose.osStatus());
1239 } catch(...) {
1240 secdebugfunc("keychain", "Unknown error");
1241 }
1242 }
1243
1244 void
1245 KeychainImpl::getAttributeInfoForItemID(CSSM_DB_RECORDTYPE itemID,
1246 SecKeychainAttributeInfo **Info)
1247 {
1248 StLock<Mutex>_(mMutex);
1249
1250 try
1251 {
1252 keychainSchema()->getAttributeInfoForRecordType(itemID, Info);
1253 }
1254 catch (const CommonError &error)
1255 {
1256 switch (error.osStatus())
1257 {
1258 case errSecNoSuchClass:
1259 case CSSMERR_DL_INVALID_RECORDTYPE:
1260 resetSchema();
1261 keychainSchema()->getAttributeInfoForRecordType(itemID, Info);
1262 default:
1263 throw;
1264 }
1265 }
1266 }
1267
1268 void
1269 KeychainImpl::freeAttributeInfo(SecKeychainAttributeInfo *Info)
1270 {
1271 free(Info->tag);
1272 free(Info->format);
1273 free(Info);
1274 }
1275
1276 CssmDbAttributeInfo
1277 KeychainImpl::attributeInfoFor(CSSM_DB_RECORDTYPE recordType, UInt32 tag)
1278 {
1279 StLock<Mutex>_(mMutex);
1280
1281 try
1282 {
1283 return keychainSchema()->attributeInfoFor(recordType, tag);
1284 }
1285 catch (const CommonError &error)
1286 {
1287 switch (error.osStatus())
1288 {
1289 case errSecNoSuchClass:
1290 case CSSMERR_DL_INVALID_RECORDTYPE:
1291 resetSchema();
1292 return keychainSchema()->attributeInfoFor(recordType, tag);
1293 default:
1294 throw;
1295 }
1296 }
1297 }
1298
1299 void
1300 KeychainImpl::recode(const CssmData &data, const CssmData &extraData)
1301 {
1302 StLock<Mutex>_(mMutex);
1303
1304 mDb->recode(data, extraData);
1305 }
1306
1307 void
1308 KeychainImpl::copyBlob(CssmData &data)
1309 {
1310 StLock<Mutex>_(mMutex);
1311
1312 mDb->copyBlob(data);
1313 }
1314
1315 void
1316 KeychainImpl::setBatchMode(Boolean mode, Boolean rollback)
1317 {
1318 StLock<Mutex>_(mMutex);
1319
1320 mDb->setBatchMode(mode, rollback);
1321 mIsInBatchMode = mode;
1322 if (!mode)
1323 {
1324 if (!rollback) // was batch mode being turned off without an abort?
1325 {
1326 // dump the buffer
1327 EventBuffer::iterator it = mEventBuffer->begin();
1328 while (it != mEventBuffer->end())
1329 {
1330 PrimaryKey primaryKey;
1331 if (it->item)
1332 {
1333 primaryKey = it->item->primaryKey();
1334 }
1335
1336 KCEventNotifier::PostKeychainEvent(it->kcEvent, mDb->dlDbIdentifier(), primaryKey);
1337
1338 ++it;
1339 }
1340
1341 }
1342
1343 // notify that a keychain has changed in too many ways to count
1344 KCEventNotifier::PostKeychainEvent(kSecKeychainLeftBatchModeEvent);
1345 mEventBuffer->clear();
1346 }
1347 else
1348 {
1349 KCEventNotifier::PostKeychainEvent(kSecKeychainEnteredBatchModeEvent);
1350 }
1351 }
1352
1353 void
1354 KeychainImpl::postEvent(SecKeychainEvent kcEvent, ItemImpl* item)
1355 {
1356 PrimaryKey primaryKey;
1357
1358 {
1359 StLock<Mutex>_(mMutex);
1360
1361 if (item != NULL)
1362 {
1363 primaryKey = item->primaryKey();
1364 }
1365 }
1366
1367 if (!mIsInBatchMode)
1368 {
1369 KCEventNotifier::PostKeychainEvent(kcEvent, mDb->dlDbIdentifier(), primaryKey);
1370 }
1371 else
1372 {
1373 StLock<Mutex>_(mMutex);
1374
1375 EventItem it;
1376 it.kcEvent = kcEvent;
1377 if (item != NULL)
1378 {
1379 it.item = item;
1380 }
1381
1382 mEventBuffer->push_back (it);
1383 }
1384 }
1385
1386
1387 bool KeychainImpl::performKeychainUpgradeIfNeeded() {
1388 // Grab this keychain's mutex. This might not be sufficient, since the
1389 // keychain might have outstanding cursors. We'll grab the RWLock later if needed.
1390 StLock<Mutex>_(mMutex);
1391
1392 if(!globals().integrityProtection()) {
1393 secdebugfunc("integrity", "skipping upgrade for %s due to global integrity protection being diabled", mDb->name());
1394 return false;
1395 }
1396
1397 // We need a CSP database for 'upgrade' to be meaningful
1398 if((mDb->dl()->subserviceMask() & CSSM_SERVICE_CSP) == 0) {
1399 return false;
1400 }
1401
1402 // We only want to upgrade file-based Apple keychains. Check the GUID.
1403 if(mDb->dl()->guid() != gGuidAppleCSPDL) {
1404 secdebugfunc("integrity", "skipping upgrade for %s due to guid mismatch\n", mDb->name());
1405 return false;
1406 }
1407
1408 // If we've already attempted an upgrade on this keychain, don't bother again
1409 if(mAttemptedUpgrade) {
1410 return false;
1411 }
1412
1413 // Don't upgrade the System root certificate keychain (to make old tp code happy)
1414 if(strncmp(mDb->name(), SYSTEM_ROOT_STORE_PATH, strlen(SYSTEM_ROOT_STORE_PATH)) == 0) {
1415 secdebugfunc("integrity", "skipping upgrade for %s\n", mDb->name());
1416 return false;
1417 }
1418
1419 uint32 dbBlobVersion = SecurityServer::DbBlob::version_MacOS_10_0;
1420
1421 try {
1422 dbBlobVersion = mDb->dbBlobVersion();
1423 } catch (CssmError cssme) {
1424 if(cssme.error == CSSMERR_DL_DATASTORE_DOESNOT_EXIST) {
1425 // oh well! We tried to get the blob version of a database
1426 // that doesn't exist. It doesn't need migration, so do nothing.
1427 secdebugfunc("integrity", "dbBlobVersion() failed for a non-existent database");
1428 return false;
1429 } else {
1430 // Some other error occurred. We can't upgrade this keychain, so fail.
1431 const char* errStr = cssmErrorString(cssme.error);
1432 secdebugfunc("integrity", "dbBlobVersion() failed for a CssmError: %d %s", (int) cssme.error, errStr);
1433 return false;
1434 }
1435 } catch (...) {
1436 secdebugfunc("integrity", "dbBlobVersion() failed for an unknown reason");
1437 return false;
1438 }
1439
1440 if(dbBlobVersion != SecurityServer::DbBlob::currentVersion) {
1441 secdebugfunc("integrity", "going to upgrade %s from version %d to %d!", mDb->name(), dbBlobVersion, SecurityServer::DbBlob::currentVersion);
1442
1443 // We need to opportunistically perform the upgrade/reload dance.
1444 //
1445 // If the keychain is unlocked, try to upgrade it.
1446 // In either case, reload the database from disk.
1447 // We need this keychain's read/write lock.
1448
1449 // Try to grab the keychain write lock.
1450 StReadWriteLock lock(mRWLock, StReadWriteLock::TryWrite);
1451
1452 // If we didn't manage to grab the lock, there's readers out there
1453 // currently reading this keychain. Abort the upgrade.
1454 if(!lock.isLocked()) {
1455 return false;
1456 }
1457
1458 try {
1459 // We can only attempt an upgrade if the keychain is currently unlocked
1460 // There's a TOCTTOU issue here, but it's going to be rare in practice, and the upgrade will simply fail.
1461 if(!mDb->isLocked()) {
1462 secdebugfunc("integrity", "attempting migration on database %s", mDb->name());
1463 // Database blob is out of date. Attempt a migration.
1464 uint32 convertedVersion = attemptKeychainMigration(dbBlobVersion, SecurityServer::DbBlob::currentVersion);
1465 if(convertedVersion == SecurityServer::DbBlob::currentVersion) {
1466 secdebugfunc("integrity", "conversion succeeded");
1467 } else {
1468 secdebugfunc("integrity", "conversion failed, keychain is still %d", convertedVersion);
1469 }
1470 }
1471 } catch (CssmError cssme) {
1472 const char* errStr = cssmErrorString(cssme.error);
1473 secdebugfunc("integrity", "caught CssmError: %d %s", (int) cssme.error, errStr);
1474 } catch (...) {
1475 // Something went wrong, but don't worry about it.
1476 }
1477
1478 // No matter if the migrator succeeded, we need to reload this keychain from disk.
1479 // Maybe someone else beat us to upgrading the keychain, but it's been locked since then.
1480 secdebugfunc("integrity", "reloading keychain");
1481 globals().storageManager.reloadKeychain(this);
1482 secdebugfunc("integrity", "database %s is version %d", mDb->name(), mDb->dbBlobVersion());
1483
1484 return true;
1485 }
1486
1487 return false;
1488 }
1489
1490 // Make sure you have this keychain's mutex and write lock when you call this function!
1491 uint32 KeychainImpl::attemptKeychainMigration(uint32 oldBlobVersion, uint32 newBlobVersion) {
1492 Db db = mDb; // let's not muck up our db for now
1493 db->takeFileLock();
1494
1495 // Let's reload this keychain to see if someone changed it on disk
1496 globals().storageManager.reloadKeychain(this);
1497
1498 if(mDb->dbBlobVersion() == newBlobVersion) {
1499 // Someone else upgraded this, hurray!
1500 secdebugfunc("integrity", "reloaded keychain version %d, quitting", mDb->dbBlobVersion());
1501 db->releaseFileLock(false);
1502 return newBlobVersion;
1503 }
1504
1505 mAttemptedUpgrade = true;
1506 uint32 newDbVersion = oldBlobVersion;
1507
1508 try {
1509 secdebugfunc("integrity", "attempting migration from version %d to %d", oldBlobVersion, newBlobVersion);
1510
1511 // First, make a backup of this database (so we never lose data if something goes wrong)
1512 mDb->makeBackup();
1513
1514 if(oldBlobVersion == SecurityServer::DbBlob::version_MacOS_10_0 && newBlobVersion == SecurityServer::DbBlob::version_partition) {
1515 // Let the upgrade begin.
1516 newDbVersion = db->recodeDbToVersion(newBlobVersion);
1517 if(newDbVersion != newBlobVersion) {
1518 // Recoding failed. Don't proceed.
1519 secdebugfunc("integrity", "recodeDbToVersion failed, version is still %d", newDbVersion);
1520 db->releaseFileLock(false);
1521 return newDbVersion;
1522 }
1523
1524 secdebugfunc("integrity", "recoded db successfully, adding extra integrity");
1525
1526 Keychain keychain(db);
1527
1528 // Breaking abstraction, but what're you going to do?
1529 // Don't upgrade this keychain, since we just upgraded the DB
1530 // But the DB won't return any new data until the txion commits
1531 keychain->mAttemptedUpgrade = true;
1532
1533 SecItemClass classes[] = {kSecGenericPasswordItemClass,
1534 kSecInternetPasswordItemClass,
1535 kSecPublicKeyItemClass,
1536 kSecPrivateKeyItemClass,
1537 kSecSymmetricKeyItemClass};
1538
1539 for(int i = 0; i < sizeof(classes) / sizeof(classes[0]); i++) {
1540 Item item;
1541 KCCursor kcc = keychain->createCursor(classes[i], NULL);
1542
1543 // During recoding, we might have deleted some corrupt keys.
1544 // Because of this, we might have zombie SSGroup records left in
1545 // the database that have no matching key. Tell the KCCursor to
1546 // delete these if found.
1547 // This will also try to suppress any other invalid items.
1548 kcc->setDeleteInvalidRecords(true);
1549
1550 while(kcc->next(item)) {
1551 try {
1552 // Force the item to set integrity. The keychain is confused about its version because it hasn't written to disk yet,
1553 // but if we've reached this point, the keychain supports integrity.
1554 item->setIntegrity(true);
1555 } catch(CssmError cssme) {
1556 // During recoding, we might have deleted some corrupt keys. Because of this, we might have zombie SSGroup records left in
1557 // the database that have no matching key. If we get a DL_RECORD_NOT_FOUND error, delete the matching item record.
1558 if (cssme.osStatus() == CSSMERR_DL_RECORD_NOT_FOUND) {
1559 secdebugfunc("integrity", "deleting corrupt (Not Found) record");
1560 keychain->deleteItem(item);
1561 } else {
1562 throw;
1563 }
1564 }
1565 }
1566 }
1567
1568 // If we reach here, tell releaseFileLock() to commit the transaction and return the new blob version
1569 secdebugfunc("integrity", "releasing file lock");
1570 db->releaseFileLock(true);
1571
1572 secdebugfunc("integrity", "success, returning version %d", newDbVersion);
1573 return newDbVersion;
1574 }
1575 } catch (CssmError cssme) {
1576 const char* errStr = cssmErrorString(cssme.error);
1577 secdebugfunc("integrity", "caught CssmError: %d %s", (int) cssme.error, errStr);
1578 db->releaseFileLock(false);
1579 } catch (MacOSError mose) {
1580 secdebugfunc("integrity", "MacOSError: %d", (int)mose.osStatus());
1581 db->releaseFileLock(false);
1582 } catch (...) {
1583 // We failed to migrate. We won't commit the transaction, so the blob on-disk stays the same.
1584 secdebugfunc("integrity", "unknown error");
1585 db->releaseFileLock(false);
1586 }
1587
1588 // If we reached here, we failed the migration. Return the old version.
1589 return oldBlobVersion;
1590 }
1591
1592
1593 Keychain::Keychain()
1594 {
1595 dispatch_once(&SecKeychainSystemKeychainChecked, ^{
1596 check_system_keychain();
1597 });
1598 }
1599
1600 Keychain::~Keychain()
1601 {
1602 }
1603
1604
1605
1606 Keychain
1607 Keychain::optional(SecKeychainRef handle)
1608 {
1609 if (handle)
1610 return KeychainImpl::required(handle);
1611 else
1612 return globals().storageManager.defaultKeychain();
1613 }
1614
1615
1616 CFIndex KeychainCore::GetKeychainRetainCount(Keychain& kc)
1617 {
1618 CFTypeRef ref = kc->handle(false);
1619 return CFGetRetainCount(ref);
1620 }
1621
1622
1623 //
1624 // Create default credentials for this keychain.
1625 // This is triggered upon default open (i.e. a Db::activate() with no set credentials).
1626 //
1627 // This function embodies the "default credentials" logic for Keychain-layer databases.
1628 //
1629 const AccessCredentials *
1630 KeychainImpl::makeCredentials()
1631 {
1632 return defaultCredentials();
1633 }
1634
1635
1636 const AccessCredentials *
1637 KeychainImpl::defaultCredentials()
1638 {
1639 StLock<Mutex>_(mMutex);
1640
1641 // Use custom unlock credentials for file keychains which have a referral
1642 // record and the standard credentials for all others.
1643
1644 if (mDb->dl()->guid() == gGuidAppleCSPDL && mCustomUnlockCreds(mDb))
1645 return &mCustomUnlockCreds;
1646 else
1647 if (mDb->dl()->guid() == gGuidAppleSdCSPDL)
1648 return globals().smartcardCredentials();
1649 else
1650 return globals().keychainCredentials();
1651 }
1652
1653
1654
1655 bool KeychainImpl::mayDelete()
1656 {
1657 return true;
1658 }
1659
1660 bool KeychainImpl::hasIntegrityProtection() {
1661 // This keychain only supports integrity if there's a database attached, that database is an Apple CSPDL, and the blob version is high enough
1662 if(mDb && (mDb->dl()->guid() == gGuidAppleCSPDL)) {
1663 if(mDb->dbBlobVersion() >= SecurityServer::DbBlob::version_partition) {
1664 return true;
1665 } else {
1666 secdebugfunc("integrity", "keychain blob version does not support integrity");
1667 return false;
1668 }
1669 } else {
1670 secdebugfunc("integrity", "keychain guid does not support integrity");
1671 return false;
1672 }
1673 return false;
1674 }
1675