+void
+ItemImpl::updateSSGroup(Db& db, CSSM_DB_RECORDTYPE recordType, CssmDataContainer* newdata, Keychain keychain, SecPointer<Access> access)
+{
+ // hhs replaced with the new aclFactory class
+ AclFactory aclFactory;
+ const AccessCredentials *nullCred = aclFactory.nullCred();
+
+ bool haveOldUniqueId = !!mUniqueId.get();
+ SSDbUniqueRecord ssUniqueId(NULL);
+ SSGroup ssGroup(NULL);
+ if(haveOldUniqueId) {
+ ssUniqueId = SSDbUniqueRecord(dynamic_cast<SSDbUniqueRecordImpl *>(&(*mUniqueId)));
+ if (ssUniqueId.get() == NULL) {
+ CssmError::throwMe(CSSMERR_CSSM_INVALID_POINTER);
+ }
+ ssGroup = ssUniqueId->group();
+ }
+
+ // If we have new data OR no old unique id, save to a new group
+ bool saveToNewSSGroup = (!!newdata) || (!haveOldUniqueId);
+
+ // If there aren't any attributes, make up some blank ones.
+ if (!mDbAttributes.get())
+ {
+ secnotice("integrity", "making new dbattributes");
+ mDbAttributes.reset(new DbAttributes());
+ mDbAttributes->recordType(mPrimaryKey->recordType());
+ }
+
+ // Add the item to the secure storage db
+ SSDbImpl* impl = dynamic_cast<SSDbImpl *>(&(*db));
+ if (impl == NULL)
+ {
+ CssmError::throwMe(CSSMERR_CSSM_INVALID_POINTER);
+ }
+
+ SSDb ssDb(impl);
+
+ TrackingAllocator allocator(Allocator::standard());
+
+ if ((!access) && (haveOldUniqueId)) {
+ // Copy the ACL from the old group.
+ secnotice("integrity", "copying old ACL");
+ access = new Access(*(ssGroup));
+
+ // We can't copy these over to the new item; they're going to be reset.
+ // Remove them before securityd complains.
+ access->removeAclsForRight(CSSM_ACL_AUTHORIZATION_PARTITION_ID);
+ access->removeAclsForRight(CSSM_ACL_AUTHORIZATION_INTEGRITY);
+ } else if (!access) {
+ secnotice("integrity", "setting up new ACL");
+ // create default access controls for the new item
+ CssmDbAttributeData *data = mDbAttributes->find(Schema::attributeInfo(kSecLabelItemAttr));
+ string printName = data ? CssmData::overlay(data->Value[0]).toString() : "keychain item";
+ access = new Access(printName);
+
+ // special case for "iTools" password - allow anyone to decrypt the item
+ if (recordType == CSSM_DL_DB_RECORD_GENERIC_PASSWORD)
+ {
+ CssmDbAttributeData *data = mDbAttributes->find(Schema::attributeInfo(kSecServiceItemAttr));
+ if (data && data->Value[0].Length == 6 && !memcmp("iTools", data->Value[0].Data, 6))
+ {
+ typedef vector<SecPointer<ACL> > AclSet;
+ AclSet acls;
+ access->findAclsForRight(CSSM_ACL_AUTHORIZATION_DECRYPT, acls);
+ for (AclSet::const_iterator it = acls.begin(); it != acls.end(); it++)
+ (*it)->form(ACL::allowAllForm);
+ }
+ }
+ } else {
+ secnotice("integrity", "passed an Access, use it");
+ // Access is non-null. Do nothing.
+ }
+
+ // If we have an old group and an old mUniqueId, then we're in the middle of an update.
+ // mDbAttributes contains the newest attributes, but not the old ones. Find
+ // them, merge them, and shove them all back into mDbAttributes. This lets
+ // us re-apply them all to the new item.
+ if(haveOldUniqueId) {
+ mDbAttributes.reset(getCurrentAttributes());
+ }
+
+ // Create a CSPDL transaction. Note that this does things when it goes out of scope.
+ CSPDLTransaction transaction(db);
+
+ Access::Maker maker;
+ ResourceControlContext prototype;
+ maker.initialOwner(prototype, nullCred);
+
+ if(saveToNewSSGroup) {
+ secnotice("integrity", "saving to a new SSGroup");
+
+ // If we're updating an item, it has an old group and possibly an
+ // old mUniqueId. Delete these from the database, so we can insert
+ // new ones.
+ if(haveOldUniqueId) {
+ secnotice("integrity", "deleting old mUniqueId");
+ mUniqueId->deleteRecord();
+ mUniqueId.release();
+ } else {
+ secnotice("integrity", "no old mUniqueId");
+ }
+
+ // Create a new SSGroup with temporary access controls
+ SSGroup newSSGroup(ssDb, &prototype);
+ const AccessCredentials * cred = maker.cred();
+
+ try {
+ doChange(keychain, recordType, ^{
+ mUniqueId = ssDb->ssInsert(recordType, mDbAttributes.get(), newdata, newSSGroup, cred);
+ });
+
+ // now finalize the access controls on the group
+ addIntegrity(*access);
+ access->setAccess(*newSSGroup, maker);
+
+ // We have to reset this after we add the integrity, since it needs the attributes
+ mDbAttributes.reset(NULL);
+
+ transaction.commit();
+ }
+ catch (CssmError cssme) {
+ const char* errStr = cssmErrorString(cssme.error);
+ secnotice("integrity", "caught CssmError during add: %d %s", (int) cssme.error, errStr);
+
+ // Delete the new SSGroup that we just created
+ deleteSSGroup(newSSGroup, nullCred);
+ throw;
+ }
+ catch (MacOSError mose) {
+ secnotice("integrity", "caught MacOSError during add: %d", (int) mose.osStatus());
+
+ deleteSSGroup(newSSGroup, nullCred);
+ throw;
+ }
+ catch (...)
+ {
+ secnotice("integrity", "caught unknown exception during add");
+
+ deleteSSGroup(newSSGroup, nullCred);
+ throw;
+ }
+ } else {
+ // Modify the old SSGroup
+ secnotice("integrity", "modifying the existing SSGroup");
+
+ try {
+ doChange(keychain, recordType, ^{
+ assert(!newdata);
+ const AccessCredentials *autoPrompt = globals().itemCredentials();
+ ssUniqueId->modify(recordType,
+ mDbAttributes.get(),
+ newdata,
+ CSSM_DB_MODIFY_ATTRIBUTE_REPLACE,
+ autoPrompt);
+ });
+
+ // Update the integrity on the SSGroup
+ setIntegrity(*ssGroup);
+
+ // We have to reset this after we add the integrity, since it needs the attributes
+ mDbAttributes.reset(NULL);
+
+ transaction.commit();
+ }
+ catch (CssmError cssme) {
+ const char* errStr = cssmErrorString(cssme.error);
+ secnotice("integrity", "caught CssmError during modify: %d %s", (int) cssme.error, errStr);
+ throw;
+ }
+ catch (MacOSError mose) {
+ secnotice("integrity", "caught MacOSError during modify: %d", (int) mose.osStatus());
+ throw;
+ }
+ catch (...)
+ {
+ secnotice("integrity", "caught unknown exception during modify");
+ throw;
+ }
+
+ }
+}
+
+// Helper function to delete a group and swallow all errors
+void ItemImpl::deleteSSGroup(SSGroup & ssgroup, const AccessCredentials* nullCred) {
+ try{
+ ssgroup->deleteKey(nullCred);
+ } catch(CssmError error) {
+ secnotice("integrity", "caught cssm error during deletion of group: %d %s", (int) error.osStatus(), error.what());
+ } catch(MacOSError error) {
+ secnotice("integrity", "caught macos error during deletion of group: %d %s", (int) error.osStatus(), error.what());
+ } catch(UnixError error) {
+ secnotice("integrity", "caught unix error during deletion of group: %d %s", (int) error.osStatus(), error.what());
+ }
+}
+
+void
+ItemImpl::doChange(Keychain keychain, CSSM_DB_RECORDTYPE recordType, void (^tryChange) ())
+{
+ // Insert the record using the newly created group.
+ try {
+ tryChange();
+ } catch (CssmError cssme) {
+ // If there's a "duplicate" of this item, it might be an item with corrupt/invalid attributes
+ // Try to extract the item and check its attributes, then try again if necessary
+ auto_ptr<CssmClient::DbAttributes> primaryKeyAttrs;
+ if(cssme.error == CSSMERR_DL_INVALID_UNIQUE_INDEX_DATA) {
+ secnotice("integrity", "possible duplicate, trying to delete invalid items");
+
+ Keychain kc = (keychain ? keychain : mKeychain);
+ if(!kc) {
+ secnotice("integrity", "no valid keychain");
+ }
+
+ // Only check for corrupt items if the keychain supports them
+ if((!kc) || !kc->hasIntegrityProtection()) {
+ secnotice("integrity", "skipping integrity check for corrupt items due to keychain support");
+ throw;
+ } else {
+ primaryKeyAttrs.reset(getCurrentAttributes());
+ PrimaryKey pk = kc->makePrimaryKey(recordType, primaryKeyAttrs.get());
+
+ bool tryAgain = false;
+
+ // Because things are lazy, maybe our keychain has a version
+ // of this item with different attributes. Ask it!
+ ItemImpl* maybeItem = kc->_lookupItem(pk);
+ if(maybeItem) {
+ if(!maybeItem->checkIntegrity()) {
+ Item item(maybeItem);
+ kc->deleteItem(item);
+ tryAgain = true;
+ }
+ } else {
+ // Our keychain doesn't know about any item with this primary key, so maybe
+ // we have a corrupt item in the database. Let's check.
+
+ secnotice("integrity", "making a cursor from primary key");
+ CssmClient::DbCursor cursor = pk->createCursor(kc);
+ DbUniqueRecord uniqueId;
+
+ StLock<Mutex> _mutexLocker(*kc->getKeychainMutex());
+
+ // The item on-disk might have more or different attributes than we do, since we're
+ // only searching via primary key. Fetch all of its attributes.
+ auto_ptr<DbAttributes>dbDupAttributes (new DbAttributes(kc->database(), 1));
+ fillDbAttributesFromSchema(*dbDupAttributes, recordType, kc);
+
+ // Occasionally this cursor won't return the item attributes (for an unknown reason).
+ // However, we know the attributes any item with this primary key should have, so use those instead.
+ while (cursor->next(dbDupAttributes.get(), NULL, uniqueId)) {
+ secnotice("integrity", "got an item...");
+
+ SSGroup group = safer_cast<SSDbUniqueRecordImpl &>(*uniqueId).group();
+ if(!ItemImpl::checkIntegrityFromDictionary(*group, dbDupAttributes.get())) {
+ secnotice("integrity", "item is invalid! deleting...");
+ uniqueId->deleteRecord();
+ tryAgain = true;
+ }
+ }
+ }
+
+ if(tryAgain) {
+ secnotice("integrity", "trying again...");
+ tryChange();
+ } else {
+ // We didn't find an invalid item, the duplicate item exception is real
+ secnotice("integrity", "duplicate item exception is real; throwing it on");
+ throw;
+ }
+ }
+ } else {
+ throw;
+ }
+ }
+}
+