]> git.saurik.com Git - apple/security.git/blob - libsecurity_keychain/lib/Keychains.cpp
Security-55179.13.tar.gz
[apple/security.git] / libsecurity_keychain / lib / Keychains.cpp
1 /*
2 * Copyright (c) 2000-2004 Apple Computer, 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 <CoreServices/../Frameworks/CarbonCore.framework/Headers/MacErrors.h>
39 #include <security_cdsa_utilities/cssmdb.h>
40 #include <security_utilities/trackingallocator.h>
41 #include <security_keychain/SecCFTypes.h>
42
43 #include "SecKeychainPriv.h"
44
45 #include <Security/SecKeychainItemPriv.h>
46 #include <CoreFoundation/CoreFoundation.h>
47 #include "DLDbListCFPref.h"
48 #include <fcntl.h>
49 #include <sys/param.h>
50 #include <syslog.h>
51 #include <sys/stat.h>
52 #include <sys/socket.h>
53 #include <sys/un.h>
54 #include <sys/types.h>
55 #include <sys/time.h>
56
57 static dispatch_once_t SecKeychainSystemKeychainChecked;
58
59 OSStatus SecKeychainSystemKeychainCheckWouldDeadlock()
60 {
61 dispatch_once(&SecKeychainSystemKeychainChecked, ^{});
62 return noErr;
63 }
64
65 using namespace KeychainCore;
66 using namespace CssmClient;
67
68
69 typedef struct EventItem
70 {
71 SecKeychainEvent kcEvent;
72 Item item;
73 } EventItem;
74
75 typedef std::list<EventItem> EventBufferSuper;
76 class EventBuffer : public EventBufferSuper
77 {
78 public:
79 EventBuffer () {}
80 virtual ~EventBuffer ();
81 };
82
83
84 EventBuffer::~EventBuffer ()
85 {
86 }
87
88
89
90 //
91 // KeychainSchemaImpl
92 //
93 KeychainSchemaImpl::KeychainSchemaImpl(const Db &db) : mMutex(Mutex::recursive)
94 {
95 DbCursor relations(db);
96 relations->recordType(CSSM_DL_DB_SCHEMA_INFO);
97 DbAttributes relationRecord(db, 1);
98 relationRecord.add(Schema::RelationID);
99 DbUniqueRecord outerUniqueId(db);
100
101 while (relations->next(&relationRecord, NULL, outerUniqueId))
102 {
103 DbUniqueRecord uniqueId(db);
104
105 uint32 relationID = relationRecord.at(0);
106 if (CSSM_DB_RECORDTYPE_SCHEMA_START <= relationID
107 && relationID < CSSM_DB_RECORDTYPE_SCHEMA_END)
108 continue;
109
110 // Create a cursor on the SCHEMA_ATTRIBUTES table for records with
111 // RelationID == relationID
112 DbCursor attributes(db);
113 attributes->recordType(CSSM_DL_DB_SCHEMA_ATTRIBUTES);
114 attributes->add(CSSM_DB_EQUAL, Schema::RelationID, relationID);
115
116 // Set up a record for retriving the SCHEMA_ATTRIBUTES
117 DbAttributes attributeRecord(db, 2);
118 attributeRecord.add(Schema::AttributeFormat);
119 attributeRecord.add(Schema::AttributeID);
120
121 RelationInfoMap &rim = mDatabaseInfoMap[relationID];
122 while (attributes->next(&attributeRecord, NULL, uniqueId))
123 rim[attributeRecord.at(1)] = attributeRecord.at(0);
124
125 // Create a cursor on the CSSM_DL_DB_SCHEMA_INDEXES table for records
126 // with RelationID == relationID
127 DbCursor indexes(db);
128 indexes->recordType(CSSM_DL_DB_SCHEMA_INDEXES);
129 indexes->conjunctive(CSSM_DB_AND);
130 indexes->add(CSSM_DB_EQUAL, Schema::RelationID, relationID);
131 indexes->add(CSSM_DB_EQUAL, Schema::IndexType,
132 uint32(CSSM_DB_INDEX_UNIQUE));
133
134 // Set up a record for retriving the SCHEMA_INDEXES
135 DbAttributes indexRecord(db, 1);
136 indexRecord.add(Schema::AttributeID);
137
138 CssmAutoDbRecordAttributeInfo &infos =
139 *new CssmAutoDbRecordAttributeInfo();
140 mPrimaryKeyInfoMap.
141 insert(PrimaryKeyInfoMap::value_type(relationID, &infos));
142 infos.DataRecordType = relationID;
143 while (indexes->next(&indexRecord, NULL, uniqueId))
144 {
145 CssmDbAttributeInfo &info = infos.add();
146 info.AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_INTEGER;
147 info.Label.AttributeID = indexRecord.at(0);
148 // @@@ Might insert bogus value if DB is corrupt
149 info.AttributeFormat = rim[info.Label.AttributeID];
150 }
151 }
152 }
153
154 KeychainSchemaImpl::~KeychainSchemaImpl()
155 {
156 try
157 {
158 for_each_map_delete(mPrimaryKeyInfoMap.begin(), mPrimaryKeyInfoMap.end());
159 }
160 catch(...)
161 {
162 }
163 }
164
165 const KeychainSchemaImpl::RelationInfoMap &
166 KeychainSchemaImpl::relationInfoMapFor(CSSM_DB_RECORDTYPE recordType) const
167 {
168 DatabaseInfoMap::const_iterator dit = mDatabaseInfoMap.find(recordType);
169 if (dit == mDatabaseInfoMap.end())
170 MacOSError::throwMe(errSecNoSuchClass);
171 return dit->second;
172 }
173
174 bool KeychainSchemaImpl::hasRecordType (CSSM_DB_RECORDTYPE recordType) const
175 {
176 DatabaseInfoMap::const_iterator it = mDatabaseInfoMap.find(recordType);
177 return it != mDatabaseInfoMap.end();
178 }
179
180 bool
181 KeychainSchemaImpl::hasAttribute(CSSM_DB_RECORDTYPE recordType, uint32 attributeId) const
182 {
183 try
184 {
185 const RelationInfoMap &rmap = relationInfoMapFor(recordType);
186 RelationInfoMap::const_iterator rit = rmap.find(attributeId);
187 return rit != rmap.end();
188 }
189 catch (MacOSError result)
190 {
191 if (result.osStatus () == errSecNoSuchClass)
192 {
193 return false;
194 }
195 else
196 {
197 throw;
198 }
199 }
200 }
201
202 CSSM_DB_ATTRIBUTE_FORMAT
203 KeychainSchemaImpl::attributeFormatFor(CSSM_DB_RECORDTYPE recordType, uint32 attributeId) const
204 {
205 const RelationInfoMap &rmap = relationInfoMapFor(recordType);
206 RelationInfoMap::const_iterator rit = rmap.find(attributeId);
207 if (rit == rmap.end())
208 MacOSError::throwMe(errSecNoSuchAttr);
209
210 return rit->second;
211 }
212
213 CssmDbAttributeInfo
214 KeychainSchemaImpl::attributeInfoFor(CSSM_DB_RECORDTYPE recordType, uint32 attributeId) const
215 {
216 CSSM_DB_ATTRIBUTE_INFO info;
217 info.AttributeFormat = attributeFormatFor(recordType, attributeId);
218 info.AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_INTEGER;
219 info.Label.AttributeID = attributeId;
220
221 return info;
222 }
223
224 void
225 KeychainSchemaImpl::getAttributeInfoForRecordType(CSSM_DB_RECORDTYPE recordType, SecKeychainAttributeInfo **Info) const
226 {
227 const RelationInfoMap &rmap = relationInfoMapFor(recordType);
228
229 SecKeychainAttributeInfo *theList=reinterpret_cast<SecKeychainAttributeInfo *>(malloc(sizeof(SecKeychainAttributeInfo)));
230
231 UInt32 capacity=rmap.size();
232 UInt32 *tagBuf=reinterpret_cast<UInt32 *>(malloc(capacity*sizeof(UInt32)));
233 UInt32 *formatBuf=reinterpret_cast<UInt32 *>(malloc(capacity*sizeof(UInt32)));
234 UInt32 i=0;
235
236
237 for (RelationInfoMap::const_iterator rit = rmap.begin(); rit != rmap.end(); ++rit)
238 {
239 if (i>=capacity)
240 {
241 capacity *= 2;
242 if (capacity <= i) capacity = i + 1;
243 tagBuf=reinterpret_cast<UInt32 *>(realloc(tagBuf, (capacity*sizeof(UInt32))));
244 formatBuf=reinterpret_cast<UInt32 *>(realloc(tagBuf, (capacity*sizeof(UInt32))));
245 }
246 tagBuf[i]=rit->first;
247 formatBuf[i++]=rit->second;
248 }
249
250 theList->count=i;
251 theList->tag=tagBuf;
252 theList->format=formatBuf;
253 *Info=theList;
254 }
255
256
257 const CssmAutoDbRecordAttributeInfo &
258 KeychainSchemaImpl::primaryKeyInfosFor(CSSM_DB_RECORDTYPE recordType) const
259 {
260 PrimaryKeyInfoMap::const_iterator it;
261 it = mPrimaryKeyInfoMap.find(recordType);
262
263 if (it == mPrimaryKeyInfoMap.end())
264 MacOSError::throwMe(errSecNoSuchClass); // @@@ Not really but whatever.
265
266 return *it->second;
267 }
268
269 bool
270 KeychainSchemaImpl::operator <(const KeychainSchemaImpl &other) const
271 {
272 return mDatabaseInfoMap < other.mDatabaseInfoMap;
273 }
274
275 bool
276 KeychainSchemaImpl::operator ==(const KeychainSchemaImpl &other) const
277 {
278 return mDatabaseInfoMap == other.mDatabaseInfoMap;
279 }
280
281 void
282 KeychainSchemaImpl::didCreateRelation(CSSM_DB_RECORDTYPE relationID,
283 const char *inRelationName,
284 uint32 inNumberOfAttributes,
285 const CSSM_DB_SCHEMA_ATTRIBUTE_INFO *pAttributeInfo,
286 uint32 inNumberOfIndexes,
287 const CSSM_DB_SCHEMA_INDEX_INFO *pIndexInfo)
288 {
289 StLock<Mutex>_(mMutex);
290
291 if (CSSM_DB_RECORDTYPE_SCHEMA_START <= relationID
292 && relationID < CSSM_DB_RECORDTYPE_SCHEMA_END)
293 return;
294
295 RelationInfoMap &rim = mDatabaseInfoMap[relationID];
296 for (uint32 ix = 0; ix < inNumberOfAttributes; ++ix)
297 rim[pAttributeInfo[ix].AttributeId] = pAttributeInfo[ix].DataType;
298
299 CssmAutoDbRecordAttributeInfo &infos =
300 *new CssmAutoDbRecordAttributeInfo();
301 mPrimaryKeyInfoMap.
302 insert(PrimaryKeyInfoMap::value_type(relationID, &infos));
303 infos.DataRecordType = relationID;
304 for (uint32 ix = 0; ix < inNumberOfIndexes; ++ix)
305 if (pIndexInfo[ix].IndexType == CSSM_DB_INDEX_UNIQUE)
306 {
307 CssmDbAttributeInfo &info = infos.add();
308 info.AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_INTEGER;
309 info.Label.AttributeID = pIndexInfo[ix].AttributeId;
310 info.AttributeFormat = rim[info.Label.AttributeID];
311 }
312 }
313
314
315 struct Event
316 {
317 SecKeychainEvent eventCode;
318 PrimaryKey primaryKey;
319 };
320 typedef std::list<Event> EventList;
321
322 #define SYSTEM_KEYCHAIN_CHECK_UNIX_BASE_NAME "/var/run/systemkeychaincheck"
323 #define SYSTEM_KEYCHAIN_CHECK_UNIX_DOMAIN_SOCKET_NAME (SYSTEM_KEYCHAIN_CHECK_UNIX_BASE_NAME ".socket")
324 #define SYSTEM_KEYCHAIN_CHECK_COMPLETE_FILE_NAME (SYSTEM_KEYCHAIN_CHECK_UNIX_BASE_NAME ".done")
325
326 static void check_system_keychain()
327 {
328 // sadly we can't use XPC here, XPC_DOMAIN_TYPE_SYSTEM doesn't exist yet. Also xpc-helper uses the
329 // keychain API (I assume for checking codesign things). So we use Unix Domain Sockets.
330
331 // NOTE: if we hit a system error we attempt to log it, and then just don't check the system keychain.
332 // In theory a system might be able to recover from this state if we let it try to muddle along, and
333 // past behaviour didn't even try this hard to do the keychain check. In particular we could be in a
334 // sandbox'ed process. So we just do our best and let another process try again.
335
336 struct stat keycheck_file_info;
337 if (stat(SYSTEM_KEYCHAIN_CHECK_COMPLETE_FILE_NAME, &keycheck_file_info) < 0) {
338 int server_fd = socket(PF_UNIX, SOCK_STREAM, 0);
339 if (server_fd < 0) {
340 syslog(LOG_ERR, "Can't get socket (%m) system keychain may be unchecked");
341 return;
342 }
343
344 struct sockaddr_un keychain_check_server_address;
345 keychain_check_server_address.sun_family = AF_UNIX;
346 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)) {
347 // It would be nice if we could compile time assert this
348 syslog(LOG_ERR, "Socket path too long, max length %d, your length %d", sizeof(keychain_check_server_address.sun_path), strlen(SYSTEM_KEYCHAIN_CHECK_UNIX_DOMAIN_SOCKET_NAME));
349 close(server_fd);
350 return;
351 }
352 keychain_check_server_address.sun_len = SUN_LEN(&keychain_check_server_address);
353
354 int rc = connect(server_fd, (struct sockaddr *)&keychain_check_server_address, keychain_check_server_address.sun_len);
355 if (rc < 0) {
356 syslog(LOG_ERR, "Can not connect to %s: %m", SYSTEM_KEYCHAIN_CHECK_UNIX_DOMAIN_SOCKET_NAME);
357 close(server_fd);
358 return;
359 }
360
361 // 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)
362 char byte;
363 ssize_t read_size = read(server_fd, &byte, 1);
364 if (read_size < 0) {
365 syslog(LOG_ERR, "Error reading from system keychain checker: %m");
366 }
367
368 close(server_fd);
369 return;
370 }
371 }
372
373 //
374 // KeychainImpl
375 //
376 KeychainImpl::KeychainImpl(const Db &db)
377 : mInCache(false), mDb(db), mCustomUnlockCreds (this), mIsInBatchMode (false), mMutex(Mutex::recursive)
378 {
379 dispatch_once(&SecKeychainSystemKeychainChecked, ^{
380 check_system_keychain();
381 });
382 mDb->defaultCredentials(this); // install activation hook
383 mEventBuffer = new EventBuffer;
384 }
385
386 KeychainImpl::~KeychainImpl()
387 {
388 try
389 {
390 // Remove ourselves from the cache if we are in it.
391 // fprintf(stderr, "Removing %p from storage manager cache.\n", handle(false));
392 globals().storageManager.removeKeychain(dlDbIdentifier(), this);
393 delete mEventBuffer;
394 }
395 catch(...)
396 {
397 }
398 }
399
400 Mutex*
401 KeychainImpl::getMutexForObject()
402 {
403 return globals().storageManager.getStorageManagerMutex();
404 }
405
406 Mutex*
407 KeychainImpl::getKeychainMutex()
408 {
409 return &mMutex;
410 }
411
412 void KeychainImpl::aboutToDestruct()
413 {
414 // remove me from the global cache, we are done
415 // fprintf(stderr, "Destructing keychain object\n");
416 DLDbIdentifier identifier = dlDbIdentifier();
417 globals().storageManager.removeKeychain(identifier, this);
418 }
419
420 bool
421 KeychainImpl::operator ==(const KeychainImpl &keychain) const
422 {
423 return dlDbIdentifier() == keychain.dlDbIdentifier();
424 }
425
426 KCCursor
427 KeychainImpl::createCursor(SecItemClass itemClass, const SecKeychainAttributeList *attrList)
428 {
429 StLock<Mutex>_(mMutex);
430
431 StorageManager::KeychainList keychains;
432 keychains.push_back(Keychain(this));
433 return KCCursor(keychains, itemClass, attrList);
434 }
435
436 KCCursor
437 KeychainImpl::createCursor(const SecKeychainAttributeList *attrList)
438 {
439 StLock<Mutex>_(mMutex);
440
441 StorageManager::KeychainList keychains;
442 keychains.push_back(Keychain(this));
443 return KCCursor(keychains, attrList);
444 }
445
446 void
447 KeychainImpl::create(UInt32 passwordLength, const void *inPassword)
448 {
449 StLock<Mutex>_(mMutex);
450
451 if (!inPassword)
452 {
453 create();
454 return;
455 }
456
457 Allocator &alloc = Allocator::standard();
458
459 // @@@ Share this instance
460
461 const CssmData password(const_cast<void *>(inPassword), passwordLength);
462 AclFactory::PasswordChangeCredentials pCreds (password, alloc);
463 AclFactory::AnyResourceContext rcc(pCreds);
464 create(&rcc);
465 }
466
467 void KeychainImpl::create(ConstStringPtr inPassword)
468 {
469 StLock<Mutex>_(mMutex);
470
471 if ( inPassword )
472 create(static_cast<UInt32>(inPassword[0]), &inPassword[1]);
473 else
474 create();
475 }
476
477 void
478 KeychainImpl::create()
479 {
480 StLock<Mutex>_(mMutex);
481
482 AclFactory aclFactory;
483 AclFactory::AnyResourceContext rcc(aclFactory.unlockCred());
484 create(&rcc);
485 }
486
487 void KeychainImpl::createWithBlob(CssmData &blob)
488 {
489 StLock<Mutex>_(mMutex);
490
491 mDb->dbInfo(&Schema::DBInfo);
492 AclFactory aclFactory;
493 AclFactory::AnyResourceContext rcc(aclFactory.unlockCred());
494 mDb->resourceControlContext (&rcc);
495 try
496 {
497 mDb->createWithBlob(blob);
498 }
499 catch (...)
500 {
501 mDb->resourceControlContext(NULL);
502 mDb->dbInfo(NULL);
503 throw;
504 }
505 mDb->resourceControlContext(NULL);
506 mDb->dbInfo(NULL); // Clear the schema (to not break an open call later)
507 globals().storageManager.created(Keychain(this));
508
509 KCEventNotifier::PostKeychainEvent (kSecKeychainListChangedEvent, this, NULL);
510 }
511
512 void
513 KeychainImpl::create(const ResourceControlContext *rcc)
514 {
515 StLock<Mutex>_(mMutex);
516
517 mDb->dbInfo(&Schema::DBInfo); // Set the schema (to force a create)
518 mDb->resourceControlContext(rcc);
519 try
520 {
521 mDb->create();
522 }
523 catch (...)
524 {
525 mDb->resourceControlContext(NULL);
526 mDb->dbInfo(NULL); // Clear the schema (to not break an open call later)
527 throw;
528 }
529 mDb->resourceControlContext(NULL);
530 mDb->dbInfo(NULL); // Clear the schema (to not break an open call later)
531 globals().storageManager.created(Keychain(this));
532 }
533
534 void
535 KeychainImpl::open()
536 {
537 StLock<Mutex>_(mMutex);
538
539 mDb->open();
540 }
541
542 void
543 KeychainImpl::lock()
544 {
545 StLock<Mutex>_(mMutex);
546
547 mDb->lock();
548 }
549
550 void
551 KeychainImpl::unlock()
552 {
553 StLock<Mutex>_(mMutex);
554
555 mDb->unlock();
556 }
557
558 void
559 KeychainImpl::unlock(const CssmData &password)
560 {
561 StLock<Mutex>_(mMutex);
562
563 mDb->unlock(password);
564 }
565
566 void
567 KeychainImpl::unlock(ConstStringPtr password)
568 {
569 StLock<Mutex>_(mMutex);
570
571 if (password)
572 {
573 const CssmData data(const_cast<unsigned char *>(&password[1]), password[0]);
574 unlock(data);
575 }
576 else
577 unlock();
578 }
579
580 void
581 KeychainImpl::getSettings(uint32 &outIdleTimeOut, bool &outLockOnSleep)
582 {
583 StLock<Mutex>_(mMutex);
584
585 mDb->getSettings(outIdleTimeOut, outLockOnSleep);
586 }
587
588 void KeychainImpl::markBlobForDotMacSyncUpdate(CssmData& data)
589 {
590 // find the plist for dot mac
591 CFArrayRef dictionaries = (CFArrayRef) CFPreferencesCopyValue(CFSTR("KeychainSyncList"),
592 CFSTR("com.apple.keychainsync"),
593 kCFPreferencesCurrentUser,
594 kCFPreferencesAnyHost);
595
596 if (dictionaries == NULL) // no such preference? The user doesn't .Mac
597 {
598 return;
599 }
600
601 CFStringRef currentPath = CFStringCreateWithCString(NULL, name(), kCFStringEncodingUTF8);
602
603 // in each dictionary look for the path to the current keychain
604 CFIndex i;
605 CFIndex count = CFArrayGetCount(dictionaries);
606
607 // make a mutable copy of the array
608 CFMutableArrayRef mutableDictionaries = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
609 for (i = 0; i < count; ++i)
610 {
611 CFMutableDictionaryRef d = (CFMutableDictionaryRef) CFDictionaryCreateMutableCopy(NULL, 0, (CFDictionaryRef) CFArrayGetValueAtIndex(dictionaries, i));
612 CFArrayAppendValue(mutableDictionaries, d);
613 CFRelease(d);
614 }
615
616 // clean up what we don't need anymore
617 CFRelease(dictionaries);
618
619 bool somethingChanged = false;
620
621 for (i = 0; i < count; ++i)
622 {
623 CFMutableDictionaryRef d = (CFMutableDictionaryRef) CFArrayGetValueAtIndex(mutableDictionaries, i);
624
625 // get the path and expand any tildes
626 CFStringRef path = (CFStringRef) CFDictionaryGetValue(d, CFSTR("DbName"));
627 CFIndex length = CFStringGetMaximumSizeForEncoding(CFStringGetLength(path), kCFStringEncodingUTF8);
628 char buffer[length + 1]; // adjust for NULL termination
629 CFStringGetCString(path, buffer, sizeof(buffer), kCFStringEncodingUTF8);
630 string fullPath = buffer;
631 fullPath = DLDbListCFPref::ExpandTildesInPath(fullPath);
632 path = CFStringCreateWithCString(NULL, fullPath.c_str(), kCFStringEncodingUTF8);
633
634 if (CFStringCompare(path, currentPath, 0) == kCFCompareEqualTo)
635 {
636 // if the value already exists, don't worry about it
637 CFDataRef oldBlob = (CFDataRef) CFDictionaryGetValue(d, CFSTR("DbOldBlob"));
638 if (oldBlob == NULL)
639 {
640 // CFify the blob
641 CFDataRef theBlob = (CFDataRef) CFDataCreate(NULL, (uint8*) data.data(), data.length());
642 CFDictionaryAddValue(d, CFSTR("DbOldBlob"), theBlob);
643 CFRelease(theBlob);
644
645 CFPreferencesSetValue(CFSTR("KeychainSyncList"),
646 mutableDictionaries,
647 CFSTR("com.apple.keychainsync"),
648 kCFPreferencesCurrentUser,
649 kCFPreferencesAnyHost);
650 somethingChanged = true;
651 }
652 }
653 }
654
655 CFRelease(currentPath);
656
657 CFRelease(mutableDictionaries);
658
659 if (somethingChanged)
660 {
661 CFPreferencesSynchronize(CFSTR("com.apple.keychainsync"), kCFPreferencesCurrentUser, kCFPreferencesAnyHost);
662 }
663 }
664
665
666
667 void
668 KeychainImpl::setSettings(uint32 inIdleTimeOut, bool inLockOnSleep)
669 {
670 StLock<Mutex>_(mMutex);
671
672 // The .Mac syncing code only makes sense for the AppleFile CSP/DL,
673 // but other DLs such as the OCSP and LDAP DLs do not expose a way to
674 // change settings or the password. To make a minimal change that only affects
675 // the smartcard case, we only look for that CSP/DL
676
677 bool isSmartcard = (mDb->dl()->guid() == gGuidAppleSdCSPDL);
678
679 // get the old keychain blob so that we can tell .Mac to resync it
680 CssmAutoData oldBlob(mDb ->allocator());
681 if (!isSmartcard)
682 mDb->copyBlob(oldBlob.get());
683
684 mDb->setSettings(inIdleTimeOut, inLockOnSleep);
685
686 // if we got here, nothing threw underneath. Send the results off to .Mac heaven
687 if (!isSmartcard)
688 markBlobForDotMacSyncUpdate(oldBlob.get());
689 }
690
691 void
692 KeychainImpl::changePassphrase(UInt32 oldPasswordLength, const void *oldPassword,
693 UInt32 newPasswordLength, const void *newPassword)
694 {
695 StLock<Mutex>_(mMutex);
696
697 bool isSmartcard = (mDb->dl()->guid() == gGuidAppleSdCSPDL);
698
699 TrackingAllocator allocator(Allocator::standard());
700 AutoCredentials cred = AutoCredentials(allocator);
701 if (oldPassword)
702 {
703 const CssmData &oldPass = *new(allocator) CssmData(const_cast<void *>(oldPassword), oldPasswordLength);
704 TypedList &oldList = *new(allocator) TypedList(allocator, CSSM_SAMPLE_TYPE_KEYCHAIN_LOCK);
705 oldList.append(new(allocator) ListElement(CSSM_SAMPLE_TYPE_PASSWORD));
706 oldList.append(new(allocator) ListElement(oldPass));
707 cred += oldList;
708 }
709
710 if (newPassword)
711 {
712 const CssmData &newPass = *new(allocator) CssmData(const_cast<void *>(newPassword), newPasswordLength);
713 TypedList &newList = *new(allocator) TypedList(allocator, CSSM_SAMPLE_TYPE_KEYCHAIN_CHANGE_LOCK);
714 newList.append(new(allocator) ListElement(CSSM_SAMPLE_TYPE_PASSWORD));
715 newList.append(new(allocator) ListElement(newPass));
716 cred += newList;
717 }
718
719 // get the old keychain blob so that we can tell .Mac to resync it
720 CssmAutoData oldBlob(mDb->allocator());
721 if (!isSmartcard)
722 mDb->copyBlob(oldBlob.get());
723
724 mDb->changePassphrase(&cred);
725
726 // if we got here, nothing threw underneath. Send the results off to .Mac heaven
727 if (!isSmartcard)
728 markBlobForDotMacSyncUpdate(oldBlob.get());
729 }
730
731 void
732 KeychainImpl::changePassphrase(ConstStringPtr oldPassword, ConstStringPtr newPassword)
733 {
734 StLock<Mutex>_(mMutex);
735
736 const void *oldPtr, *newPtr;
737 UInt32 oldLen, newLen;
738 if (oldPassword)
739 {
740 oldLen = oldPassword[0];
741 oldPtr = oldPassword + 1;
742 }
743 else
744 {
745 oldLen = 0;
746 oldPtr = NULL;
747 }
748
749 if (newPassword)
750 {
751 newLen = newPassword[0];
752 newPtr = newPassword + 1;
753 }
754 else
755 {
756 newLen = 0;
757 newPtr = NULL;
758 }
759
760 changePassphrase(oldLen, oldPtr, newLen, newPtr);
761 }
762
763 void
764 KeychainImpl::authenticate(const CSSM_ACCESS_CREDENTIALS *cred)
765 {
766 StLock<Mutex>_(mMutex);
767
768 if (!exists())
769 MacOSError::throwMe(errSecNoSuchKeychain);
770
771 MacOSError::throwMe(unimpErr);
772 }
773
774 UInt32
775 KeychainImpl::status() const
776 {
777 // @@@ We should figure out the read/write status though a DL passthrough
778 // or some other way. Also should locked be unlocked read only or just
779 // read-only?
780 return (mDb->isLocked() ? 0 : kSecUnlockStateStatus | kSecWritePermStatus)
781 | kSecReadPermStatus;
782 }
783
784 bool
785 KeychainImpl::exists()
786 {
787 StLock<Mutex>_(mMutex);
788
789 bool exists = true;
790 try
791 {
792 open();
793 // Ok to leave the mDb open since it will get closed when it goes away.
794 }
795 catch (const CssmError &e)
796 {
797 if (e.osStatus() != CSSMERR_DL_DATASTORE_DOESNOT_EXIST)
798 throw;
799 exists = false;
800 }
801
802 return exists;
803 }
804
805 bool
806 KeychainImpl::isActive() const
807 {
808 return mDb->isActive();
809 }
810
811 void KeychainImpl::completeAdd(Item &inItem, PrimaryKey &primaryKey)
812 {
813
814
815 // The inItem shouldn't be in the cache yet
816 assert(!inItem->inCache());
817
818 // Insert inItem into mDbItemMap with key primaryKey. p.second will be
819 // true if it got inserted. If not p.second will be false and p.first
820 // will point to the current entry with key primaryKey.
821 pair<DbItemMap::iterator, bool> p =
822 mDbItemMap.insert(DbItemMap::value_type(primaryKey, inItem.get()));
823 if (!p.second)
824 {
825 // There was already an ItemImpl * in mDbItemMap with key
826 // primaryKey. Get a ref to the pointer to it so we can assign a
827 // new value to it below.
828 ItemImpl *oldItem = p.first->second;
829
830 // @@@ If this happens we are breaking our API contract of
831 // uniquifying items. We really need to insert the item into the
832 // map before we start the add. And have the item be in an
833 // "is being added" state.
834 assert(oldItem->inCache());
835 secdebug("keychain", "add of new item %p somehow replaced %p",
836 inItem.get(), oldItem);
837 oldItem->inCache(false);
838 oldItem = inItem.get();
839 }
840
841 inItem->inCache(true);
842 }
843
844 void
845 KeychainImpl::addCopy(Item &inItem)
846 {
847 Keychain keychain(this);
848 PrimaryKey primaryKey = inItem->addWithCopyInfo(keychain, true);
849 completeAdd(inItem, primaryKey);
850 postEvent(kSecAddEvent, inItem);
851 }
852
853 void
854 KeychainImpl::add(Item &inItem)
855 {
856 Keychain keychain(this);
857 PrimaryKey primaryKey = inItem->add(keychain);
858 completeAdd(inItem, primaryKey);
859 postEvent(kSecAddEvent, inItem);
860 }
861
862 void
863 KeychainImpl::didUpdate(const Item &inItem, PrimaryKey &oldPK,
864 PrimaryKey &newPK)
865 {
866 // If the primary key hasn't changed we don't need to update mDbItemMap.
867 if (oldPK != newPK)
868 {
869 // If inItem isn't in the cache we don't need to update mDbItemMap.
870 assert(inItem->inCache());
871 if (inItem->inCache())
872 {
873 // First remove the entry for inItem in mDbItemMap with key oldPK.
874 DbItemMap::iterator it = mDbItemMap.find(oldPK);
875 if (it != mDbItemMap.end() && (ItemImpl*) it->second == inItem.get())
876 mDbItemMap.erase(it);
877
878 // Insert inItem into mDbItemMap with key newPK. p.second will be
879 // true if it got inserted. If not p.second will be false and
880 // p.first will point to the current entry with key newPK.
881 pair<DbItemMap::iterator, bool> p =
882 mDbItemMap.insert(DbItemMap::value_type(newPK, inItem.get()));
883 if (!p.second)
884 {
885 // There was already an ItemImpl * in mDbItemMap with key
886 // primaryKey. Get a ref to the pointer to it so we can assign
887 // a new value to it below.
888 ItemImpl *oldItem = p.first->second;
889
890 // @@@ If this happens we are breaking our API contract of
891 // uniquifying items. We really need to insert the item into
892 // the map with the new primary key before we start the update.
893 // And have the item be in an "is being updated" state.
894 assert(oldItem->inCache());
895 secdebug("keychain", "update of item %p somehow replaced %p",
896 inItem.get(), oldItem);
897 oldItem->inCache(false);
898 oldItem = inItem.get();
899 }
900 }
901 }
902
903 postEvent(kSecUpdateEvent, inItem);
904 }
905
906 void
907 KeychainImpl::deleteItem(Item &inoutItem)
908 {
909 {
910 // We don't need to hold the DO mutex through event posting, and, in fact, doing so causes deadlock.
911 // Hold it only as long as needed, instead.
912
913
914 // item must be persistent
915 if (!inoutItem->isPersistent())
916 MacOSError::throwMe(errSecInvalidItemRef);
917
918 DbUniqueRecord uniqueId = inoutItem->dbUniqueRecord();
919 PrimaryKey primaryKey = inoutItem->primaryKey();
920 uniqueId->deleteRecord();
921
922 // Don't remove the item from the mDbItemMap here since this would cause
923 // us to report a new item to our caller when we receive the
924 // kSecDeleteEvent notification.
925 // It will be removed before we post the notification, because
926 // CCallbackMgr will call didDeleteItem()
927
928 // Post the notification for the item deletion with
929 // the primaryKey obtained when the item still existed
930 }
931
932 postEvent(kSecDeleteEvent, inoutItem);
933 }
934
935
936 CssmClient::CSP
937 KeychainImpl::csp()
938 {
939 StLock<Mutex>_(mMutex);
940
941 if (!mDb->dl()->subserviceMask() & CSSM_SERVICE_CSP)
942 MacOSError::throwMe(errSecInvalidKeychain);
943
944 // Try to cast first to a CSPDL to handle case where we don't have an SSDb
945 try
946 {
947 CssmClient::CSPDL cspdl(dynamic_cast<CssmClient::CSPDLImpl *>(&*mDb->dl()));
948 return CSP(cspdl);
949 }
950 catch (...)
951 {
952 SSDbImpl* impl = dynamic_cast<SSDbImpl *>(&(*mDb));
953 if (impl == NULL)
954 {
955 CssmError::throwMe(CSSMERR_CSSM_INVALID_POINTER);
956 }
957
958 SSDb ssDb(impl);
959 return ssDb->csp();
960 }
961 }
962
963 PrimaryKey
964 KeychainImpl::makePrimaryKey(CSSM_DB_RECORDTYPE recordType, DbUniqueRecord &uniqueId)
965 {
966 StLock<Mutex>_(mMutex);
967
968 DbAttributes primaryKeyAttrs(uniqueId->database());
969 primaryKeyAttrs.recordType(recordType);
970 gatherPrimaryKeyAttributes(primaryKeyAttrs);
971 uniqueId->get(&primaryKeyAttrs, NULL);
972 return PrimaryKey(primaryKeyAttrs);
973 }
974
975 const CssmAutoDbRecordAttributeInfo &
976 KeychainImpl::primaryKeyInfosFor(CSSM_DB_RECORDTYPE recordType)
977 {
978 StLock<Mutex>_(mMutex);
979
980 try
981 {
982 return keychainSchema()->primaryKeyInfosFor(recordType);
983 }
984 catch (const CommonError &error)
985 {
986 switch (error.osStatus())
987 {
988 case errSecNoSuchClass:
989 case CSSMERR_DL_INVALID_RECORDTYPE:
990 resetSchema();
991 return keychainSchema()->primaryKeyInfosFor(recordType);
992 default:
993 throw;
994 }
995 }
996 }
997
998 void KeychainImpl::gatherPrimaryKeyAttributes(DbAttributes& primaryKeyAttrs)
999 {
1000 StLock<Mutex>_(mMutex);
1001
1002 const CssmAutoDbRecordAttributeInfo &infos =
1003 primaryKeyInfosFor(primaryKeyAttrs.recordType());
1004
1005 // @@@ fix this to not copy info.
1006 for (uint32 i = 0; i < infos.size(); i++)
1007 primaryKeyAttrs.add(infos.at(i));
1008 }
1009
1010 ItemImpl *
1011 KeychainImpl::_lookupItem(const PrimaryKey &primaryKey)
1012 {
1013 DbItemMap::iterator it = mDbItemMap.find(primaryKey);
1014 if (it != mDbItemMap.end())
1015 {
1016 if (it->second == NULL)
1017 {
1018 // we've been weak released...
1019 mDbItemMap.erase(it);
1020 }
1021 else
1022 {
1023 return it->second;
1024 }
1025 }
1026
1027 return NULL;
1028 }
1029
1030 Item
1031 KeychainImpl::item(const PrimaryKey &primaryKey)
1032 {
1033 StLock<Mutex>_(mMutex);
1034
1035 // Lookup the item in the map while holding the apiLock.
1036 ItemImpl *itemImpl = _lookupItem(primaryKey);
1037 if (itemImpl)
1038 return Item(itemImpl);
1039
1040 try
1041 {
1042 // We didn't find it so create a new item with just a keychain and
1043 // a primary key. However since we aren't holding
1044 // globals().apiLock anymore some other thread might have beaten
1045 // us to creating this item and adding it to the cache. If that
1046 // happens we retry the lookup.
1047 return Item(this, primaryKey);
1048 }
1049 catch (const MacOSError &e)
1050 {
1051 // If the item creation failed because some other thread already
1052 // inserted this item into the cache we retry the lookup.
1053 if (e.osStatus() == errSecDuplicateItem)
1054 {
1055 // Lookup the item in the map while holding the apiLock.
1056 ItemImpl *itemImpl = _lookupItem(primaryKey);
1057 if (itemImpl)
1058 return Item(itemImpl);
1059 }
1060 throw;
1061 }
1062 }
1063
1064
1065 Item
1066 KeychainImpl::item(CSSM_DB_RECORDTYPE recordType, DbUniqueRecord &uniqueId)
1067 {
1068 StLock<Mutex>_(mMutex);
1069
1070 PrimaryKey primaryKey = makePrimaryKey(recordType, uniqueId);
1071 {
1072 // Lookup the item in the map while holding the apiLock.
1073 ItemImpl *itemImpl = _lookupItem(primaryKey);
1074
1075 if (itemImpl)
1076 {
1077 return Item(itemImpl);
1078 }
1079 }
1080
1081 try
1082 {
1083 // We didn't find it so create a new item with a keychain, a primary key
1084 // and a DbUniqueRecord. However since we aren't holding
1085 // globals().apiLock anymore some other thread might have beaten
1086 // us to creating this item and adding it to the cache. If that
1087 // happens we retry the lookup.
1088 return Item(this, primaryKey, uniqueId);
1089 }
1090 catch (const MacOSError &e)
1091 {
1092 // If the item creation failed because some other thread already
1093 // inserted this item into the cache we retry the lookup.
1094 if (e.osStatus() == errSecDuplicateItem)
1095 {
1096 // Lookup the item in the map while holding the apiLock.
1097 ItemImpl *itemImpl = _lookupItem(primaryKey);
1098 if (itemImpl)
1099 return Item(itemImpl);
1100 }
1101 throw;
1102 }
1103 }
1104
1105 KeychainSchema
1106 KeychainImpl::keychainSchema()
1107 {
1108 StLock<Mutex>_(mMutex);
1109 if (!mKeychainSchema)
1110 mKeychainSchema = KeychainSchema(mDb);
1111
1112 return mKeychainSchema;
1113 }
1114
1115 void KeychainImpl::resetSchema()
1116 {
1117 mKeychainSchema = NULL; // re-fetch it from db next time
1118 }
1119
1120
1121 // Called from DbItemImpl's constructor (so it is only partially constructed),
1122 // add it to the map.
1123 void
1124 KeychainImpl::addItem(const PrimaryKey &primaryKey, ItemImpl *dbItemImpl)
1125 {
1126 StLock<Mutex>_(mMutex);
1127
1128 // The dbItemImpl shouldn't be in the cache yet
1129 assert(!dbItemImpl->inCache());
1130
1131 // Insert dbItemImpl into mDbItemMap with key primaryKey. p.second will
1132 // be true if it got inserted. If not p.second will be false and p.first
1133 // will point to the current entry with key primaryKey.
1134 pair<DbItemMap::iterator, bool> p =
1135 mDbItemMap.insert(DbItemMap::value_type(primaryKey, dbItemImpl));
1136
1137 if (!p.second)
1138 {
1139 // There was already an ItemImpl * in mDbItemMap with key primaryKey.
1140 // There is a race condition here when being called in multiple threads
1141 // We might have added an item using add and received a notification at
1142 // the same time.
1143 MacOSError::throwMe(errSecDuplicateItem);
1144 }
1145
1146 dbItemImpl->inCache(true);
1147 }
1148
1149 void
1150 KeychainImpl::didDeleteItem(ItemImpl *inItemImpl)
1151 {
1152 StLock<Mutex>_(mMutex);
1153
1154 // Called by CCallbackMgr
1155 secdebug("kcnotify", "%p notified that item %p was deleted", this, inItemImpl);
1156 removeItem(inItemImpl->primaryKey(), inItemImpl);
1157 }
1158
1159 void
1160 KeychainImpl::removeItem(const PrimaryKey &primaryKey, ItemImpl *inItemImpl)
1161 {
1162 StLock<Mutex>_(mMutex);
1163
1164 // If inItemImpl isn't in the cache to begin with we are done.
1165 if (!inItemImpl->inCache())
1166 return;
1167
1168 DbItemMap::iterator it = mDbItemMap.find(primaryKey);
1169 if (it != mDbItemMap.end() && (ItemImpl*) it->second == inItemImpl)
1170 mDbItemMap.erase(it);
1171
1172 inItemImpl->inCache(false);
1173 }
1174
1175 void
1176 KeychainImpl::getAttributeInfoForItemID(CSSM_DB_RECORDTYPE itemID,
1177 SecKeychainAttributeInfo **Info)
1178 {
1179 StLock<Mutex>_(mMutex);
1180
1181 try
1182 {
1183 keychainSchema()->getAttributeInfoForRecordType(itemID, Info);
1184 }
1185 catch (const CommonError &error)
1186 {
1187 switch (error.osStatus())
1188 {
1189 case errSecNoSuchClass:
1190 case CSSMERR_DL_INVALID_RECORDTYPE:
1191 resetSchema();
1192 keychainSchema()->getAttributeInfoForRecordType(itemID, Info);
1193 default:
1194 throw;
1195 }
1196 }
1197 }
1198
1199 void
1200 KeychainImpl::freeAttributeInfo(SecKeychainAttributeInfo *Info)
1201 {
1202 free(Info->tag);
1203 free(Info->format);
1204 free(Info);
1205 }
1206
1207 CssmDbAttributeInfo
1208 KeychainImpl::attributeInfoFor(CSSM_DB_RECORDTYPE recordType, UInt32 tag)
1209 {
1210 StLock<Mutex>_(mMutex);
1211
1212 try
1213 {
1214 return keychainSchema()->attributeInfoFor(recordType, tag);
1215 }
1216 catch (const CommonError &error)
1217 {
1218 switch (error.osStatus())
1219 {
1220 case errSecNoSuchClass:
1221 case CSSMERR_DL_INVALID_RECORDTYPE:
1222 resetSchema();
1223 return keychainSchema()->attributeInfoFor(recordType, tag);
1224 default:
1225 throw;
1226 }
1227 }
1228 }
1229
1230 void
1231 KeychainImpl::recode(const CssmData &data, const CssmData &extraData)
1232 {
1233 StLock<Mutex>_(mMutex);
1234
1235 mDb->recode(data, extraData);
1236 }
1237
1238 void
1239 KeychainImpl::copyBlob(CssmData &data)
1240 {
1241 StLock<Mutex>_(mMutex);
1242
1243 mDb->copyBlob(data);
1244 }
1245
1246 void
1247 KeychainImpl::setBatchMode(Boolean mode, Boolean rollback)
1248 {
1249 StLock<Mutex>_(mMutex);
1250
1251 mDb->setBatchMode(mode, rollback);
1252 mIsInBatchMode = mode;
1253 if (!mode)
1254 {
1255 if (!rollback) // was batch mode being turned off without an abort?
1256 {
1257 // dump the buffer
1258 EventBuffer::iterator it = mEventBuffer->begin();
1259 while (it != mEventBuffer->end())
1260 {
1261 PrimaryKey primaryKey;
1262 if (it->item)
1263 {
1264 primaryKey = it->item->primaryKey();
1265 }
1266
1267 KCEventNotifier::PostKeychainEvent(it->kcEvent, mDb->dlDbIdentifier(), primaryKey);
1268
1269 ++it;
1270 }
1271
1272 }
1273
1274 // notify that a keychain has changed in too many ways to count
1275 KCEventNotifier::PostKeychainEvent(kSecKeychainLeftBatchModeEvent);
1276 mEventBuffer->clear();
1277 }
1278 else
1279 {
1280 KCEventNotifier::PostKeychainEvent(kSecKeychainEnteredBatchModeEvent);
1281 }
1282 }
1283
1284 void
1285 KeychainImpl::postEvent(SecKeychainEvent kcEvent, ItemImpl* item)
1286 {
1287 PrimaryKey primaryKey;
1288
1289 {
1290 StLock<Mutex>_(mMutex);
1291
1292 if (item != NULL)
1293 {
1294 primaryKey = item->primaryKey();
1295 }
1296 }
1297
1298 if (!mIsInBatchMode)
1299 {
1300 KCEventNotifier::PostKeychainEvent(kcEvent, mDb->dlDbIdentifier(), primaryKey);
1301 }
1302 else
1303 {
1304 StLock<Mutex>_(mMutex);
1305
1306 EventItem it;
1307 it.kcEvent = kcEvent;
1308 if (item != NULL)
1309 {
1310 it.item = item;
1311 }
1312
1313 mEventBuffer->push_back (it);
1314 }
1315 }
1316
1317 Keychain::Keychain()
1318 {
1319 dispatch_once(&SecKeychainSystemKeychainChecked, ^{
1320 check_system_keychain();
1321 });
1322 }
1323
1324 Keychain
1325 Keychain::optional(SecKeychainRef handle)
1326 {
1327 if (handle)
1328 return KeychainImpl::required(handle);
1329 else
1330 return globals().storageManager.defaultKeychain();
1331 }
1332
1333
1334 CFIndex GetKeychainRetainCount(Keychain& kc)
1335 {
1336 CFTypeRef ref = kc->handle(false);
1337 return CFGetRetainCount(ref);
1338 }
1339
1340
1341 //
1342 // Create default credentials for this keychain.
1343 // This is triggered upon default open (i.e. a Db::activate() with no set credentials).
1344 //
1345 // This function embodies the "default credentials" logic for Keychain-layer databases.
1346 //
1347 const AccessCredentials *
1348 KeychainImpl::makeCredentials()
1349 {
1350 return defaultCredentials();
1351 }
1352
1353
1354 const AccessCredentials *
1355 KeychainImpl::defaultCredentials()
1356 {
1357 StLock<Mutex>_(mMutex);
1358
1359 // Use custom unlock credentials for file keychains which have a referral
1360 // record and the standard credentials for all others.
1361
1362 if (mDb->dl()->guid() == gGuidAppleCSPDL && mCustomUnlockCreds(mDb))
1363 return &mCustomUnlockCreds;
1364 else
1365 if (mDb->dl()->guid() == gGuidAppleSdCSPDL)
1366 return globals().smartcardCredentials();
1367 else
1368 return globals().keychainCredentials();
1369 }
1370
1371
1372