+void KeyItem::setIntegrity(bool force) {
+ ItemImpl::setIntegrity(*key(), force);
+}
+
+bool KeyItem::checkIntegrity() {
+ if(!isPersistent()) {
+ return true;
+ }
+
+ return ItemImpl::checkIntegrity(*key());
+}
+
+// KeyItems are a little bit special: the only modifications you can do to them
+// is to change their Print Name, Label, or Application Tag.
+//
+// When we do this modification, we need to look ahead to see if there's an item
+// that's already there. If there are, we're going to throw a errSecDuplicateItem.
+//
+// Unless that item doesn't pass the integrity check, in which case we delete it
+// and continue with the add.
+void KeyItem::modifyUniqueId(Keychain keychain, SSDb ssDb, DbUniqueRecord& uniqueId, DbAttributes& newDbAttributes, CSSM_DB_RECORDTYPE recordType) {
+ SSDbCursor otherDbCursor(ssDb, 1);
+ otherDbCursor->recordType(recordType);
+
+ bool checkForDuplicates = false;
+ // Set up the ssdb cursor
+ CssmDbAttributeData* label = newDbAttributes.findAttribute(kInfoKeyLabel);
+ if(label) {
+ otherDbCursor->add(CSSM_DB_EQUAL, kInfoKeyLabel, label->at(0));
+ checkForDuplicates = true;
+ }
+ CssmDbAttributeData* apptag = newDbAttributes.findAttribute(kInfoKeyApplicationTag);
+ if(apptag) {
+ otherDbCursor->add(CSSM_DB_EQUAL, kInfoKeyApplicationTag, apptag->at(0));
+ checkForDuplicates = true;
+ }
+
+ // KeyItems only have integrity if the keychain supports it; otherwise,
+ // don't pre-check for duplicates
+ if((!keychain) || !keychain->hasIntegrityProtection()) {
+ secdebugfunc("integrity", "key skipping duplicate integrity check due to keychain version");
+ checkForDuplicates = false;
+ }
+
+ if (checkForDuplicates) {
+ secdebugfunc("integrity", "looking for duplicates");
+ // If there are duplicates that are invalid, delete it and
+ // continue. Otherwise, if there are duplicates, throw errSecDuplicateItem.
+ DbAttributes otherDbAttributes;
+ DbUniqueRecord otherUniqueId;
+ CssmClient::Key otherKey;
+
+ while(otherDbCursor->nextKey(&otherDbAttributes, otherKey, otherUniqueId)) {
+ secdebugfunc("integrity", "found a duplicate, checking integrity");
+
+ PrimaryKey pk = keychain->makePrimaryKey(recordType, otherUniqueId);
+
+ ItemImpl* maybeItem = keychain->_lookupItem(pk);
+ if(maybeItem) {
+ if(maybeItem->checkIntegrity()) {
+ secdebugfunc("integrity", "duplicate is real, throwing error");
+ MacOSError::throwMe(errSecDuplicateItem);
+ } else {
+ secdebugfunc("integrity", "existing duplicate item is invalid, removing...");
+ Item item(maybeItem);
+ keychain->deleteItem(item);
+ }
+ } else {
+ KeyItem temp(keychain, pk, otherUniqueId);
+
+ if(temp.checkIntegrity()) {
+ secdebugfunc("integrity", "duplicate is real, throwing error");
+ MacOSError::throwMe(errSecDuplicateItem);
+ } else {
+ secdebugfunc("integrity", "duplicate is invalid, removing");
+ // Keychain's idea of deleting items involves notifications and callbacks. We don't want that,
+ // (since this isn't a real item and it should go away quietly), so use this roundabout method.
+ otherUniqueId->deleteRecord();
+ keychain->removeItem(temp.primaryKey(), &temp);
+ }
+ }
+ }
+ }
+
+ try {
+ secdebugfunc("integrity", "modifying unique id");
+ uniqueId->modify(recordType, &newDbAttributes, NULL, CSSM_DB_MODIFY_ATTRIBUTE_REPLACE);
+ secdebugfunc("integrity", "done modifying unique id");
+ } catch(CssmError e) {
+ // Just in case something went wrong, clean up after this add
+ uniqueId->deleteRecord();
+ throw;
+ }
+}