+void ItemImpl::fillDbAttributesFromSchema(DbAttributes& dbAttributes, CSSM_DB_RECORDTYPE recordType, Keychain keychain) {
+ // If we weren't passed a keychain, use our own.
+ keychain = !!keychain ? keychain : mKeychain;
+
+ // Without a keychain, there's no one to ask.
+ if(!keychain) {
+ return;
+ }
+
+ SecKeychainAttributeInfo* infos;
+ keychain->getAttributeInfoForItemID(recordType, &infos);
+
+ secinfo("integrity", "filling %u attributes for type %u", (unsigned int)infos->count, recordType);
+
+ for (uint32 i = 0; i < infos->count; i++) {
+ CSSM_DB_ATTRIBUTE_INFO info;
+ memset(&info, 0, sizeof(info));
+
+ info.AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_INTEGER;
+ info.Label.AttributeID = infos->tag[i];
+ info.AttributeFormat = infos->format[i];
+
+ dbAttributes.add(info);
+ }
+
+ keychain->freeAttributeInfo(infos);
+}
+
+DbAttributes* ItemImpl::getCurrentAttributes() {
+ DbAttributes* dbAttributes;
+ secinfo("integrity", "getting current attributes...");
+
+ if(mUniqueId.get()) {
+ // If we have a unique id, there's an item in the database backing us. Ask for its attributes.
+ dbAttributes = new DbAttributes(dbUniqueRecord()->database(), 1);
+ fillDbAttributesFromSchema(*dbAttributes, recordType());
+ mUniqueId->get(dbAttributes, NULL);
+
+ // and fold in any updates.
+ if(mDbAttributes.get()) {
+ secinfo("integrity", "adding %d attributes from mDbAttributes", mDbAttributes->size());
+ dbAttributes->updateWithDbAttributes(&(*mDbAttributes.get()));
+ }
+ } else if (mDbAttributes.get()) {
+ // We don't have a backing item, so all our attributes are in mDbAttributes. Copy them.
+ secnotice("integrity", "no unique id, using %d attributes from mDbAttributes", mDbAttributes->size());
+ dbAttributes = new DbAttributes();
+ dbAttributes->updateWithDbAttributes(&(*mDbAttributes.get()));
+ } else {
+ // No attributes at all. We should maybe throw here, but let's not.
+ secnotice("integrity", "no attributes at all");
+ dbAttributes = new DbAttributes();
+ }
+ dbAttributes->recordType(recordType());
+ // TODO: We don't set semanticInformation. Issue?
+
+ return dbAttributes;
+}
+
+
+void ItemImpl::encodeAttributes(CssmOwnedData &attributeBlob) {
+ // Sometimes we don't have our attributes. Find them.
+ auto_ptr<DbAttributes> dbAttributes(getCurrentAttributes());
+ encodeAttributesFromDictionary(attributeBlob, dbAttributes.get());
+
+}
+
+void ItemImpl::encodeAttributesFromDictionary(CssmOwnedData &attributeBlob, DbAttributes* dbAttributes) {
+ // Create a CFDictionary from dbAttributes and call der_encode_dictionary on it
+ CFRef<CFMutableDictionaryRef> attributes;
+ attributes.take(CFDictionaryCreateMutable(NULL, dbAttributes->size(), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
+
+ secinfo("integrity", "looking at %d attributes", dbAttributes->size());
+ // TODO: include record type and semantic information?
+
+ for(int i = 0; i < dbAttributes->size(); i++) {
+ CssmDbAttributeData& data = dbAttributes->attributes()[i];
+ CssmDbAttributeInfo& datainfo = data.info();
+
+ // Sometimes we need to normalize the info. Calling Schema::attributeInfo is the best way to do that.
+ // There's no avoiding the try-catch structure here, since only some of the names are in Schema::attributeInfo,
+ // but it will only indicate that by throwing.
+ CssmDbAttributeInfo& actualInfo = datainfo;
+ try {
+ if(datainfo.nameFormat() == CSSM_DB_ATTRIBUTE_NAME_AS_INTEGER && Schema::haveAttributeInfo(datainfo.intName())) {
+ actualInfo = Schema::attributeInfo(datainfo.intName());
+ }
+ } catch(...) {
+ actualInfo = datainfo;
+ }
+
+ // Pull the label/name out of this data
+ CFRef<CFDataRef> label = NULL;
+
+ switch(actualInfo.nameFormat()) {
+ case CSSM_DB_ATTRIBUTE_NAME_AS_STRING: {
+ const char* stringname = actualInfo.stringName();
+ label.take(CFDataCreate(NULL, reinterpret_cast<const UInt8*>(stringname), strlen(stringname)));
+ break;
+ }
+ case CSSM_DB_ATTRIBUTE_NAME_AS_OID: {
+ const CssmOid& oidname = actualInfo.oidName();
+ label.take(CFDataCreate(NULL, reinterpret_cast<const UInt8*>(oidname.data()), oidname.length()));
+ break;
+ }
+ case CSSM_DB_ATTRIBUTE_NAME_AS_INTEGER: {
+ uint32 iname = actualInfo.intName();
+ label.take(CFDataCreate(NULL, reinterpret_cast<const UInt8*>(&(iname)), sizeof(uint32)));
+ break;
+ }
+ }
+
+ if(data.size() == 0) {
+ // This attribute doesn't have a value, and so shouldn't be included in the digest.
+ continue;
+ }
+
+ // Do not include the Creation or Modification date attributes in the hash.
+ // Use this complicated method of checking so we'll catch string and integer names.
+ SecKeychainAttrType cdat = kSecCreationDateItemAttr;
+ SecKeychainAttrType cmod = kSecModDateItemAttr;
+ if((CFDataGetLength(label) == sizeof(SecKeychainAttrType)) &&
+ ((memcmp(CFDataGetBytePtr(label), &cdat, sizeof(SecKeychainAttrType)) == 0) ||
+ (memcmp(CFDataGetBytePtr(label), &cmod, sizeof(SecKeychainAttrType)) == 0))) {
+ continue;
+ }
+
+ // Collect the raw data for each value of this CssmDbAttributeData
+ CFRef<CFMutableArrayRef> attributeDataContainer;
+ attributeDataContainer.take(CFArrayCreateMutable(NULL, data.size(), &kCFTypeArrayCallBacks));
+
+ for(int j = 0; j < data.size(); j++) {
+ CssmData& entry = data.values()[j];
+
+ CFRef<CFDataRef> datadata = NULL;
+ switch(actualInfo.format()) {
+ case CSSM_DB_ATTRIBUTE_FORMAT_BLOB:
+ case CSSM_DB_ATTRIBUTE_FORMAT_STRING:
+ case CSSM_DB_ATTRIBUTE_FORMAT_TIME_DATE:
+ datadata.take(CFDataCreate(NULL, reinterpret_cast<const UInt8*>(data.values()[j].data()), data.values()[j].length()));
+ break;
+
+ case CSSM_DB_ATTRIBUTE_FORMAT_UINT32: {
+ uint32 x = entry.length() == 1 ? *reinterpret_cast<uint8 *>(entry.Data) :
+ entry.length() == 2 ? *reinterpret_cast<uint16 *>(entry.Data) :
+ entry.length() == 4 ? *reinterpret_cast<uint32 *>(entry.Data) : 0;
+ datadata.take(CFDataCreate(NULL, reinterpret_cast<const UInt8*>(&x), sizeof(x)));
+ break;
+ }
+
+ case CSSM_DB_ATTRIBUTE_FORMAT_SINT32: {
+ sint32 x = entry.length() == 1 ? *reinterpret_cast<sint8 *>(entry.Data) :
+ entry.length() == 2 ? *reinterpret_cast<sint16 *>(entry.Data) :
+ entry.length() == 4 ? *reinterpret_cast<sint32 *>(entry.Data) : 0;
+ datadata.take(CFDataCreate(NULL, reinterpret_cast<const UInt8*>(&x), sizeof(x)));
+ break;
+ }
+ // CSSM_DB_ATTRIBUTE_FORMAT_BIG_NUM is unimplemented here but
+ // has some canonicalization requirements, see DbValue.cpp
+
+ default:
+ continue;
+ }
+
+ CFArrayAppendValue(attributeDataContainer, datadata);
+ }
+ CFDictionaryAddValue(attributes, label, attributeDataContainer);
+ }
+
+ // Now that we have a CFDictionary containing a bunch of CFDatas, turn that
+ // into a der blob.
+
+ CFErrorRef error;
+ CFRef<CFDataRef> derBlob;
+ derBlob.take(CFPropertyListCreateDERData(NULL, attributes, &error));
+
+ // TODO: How do we check error here?
+
+ if(!derBlob) {
+ return;
+ }
+
+ attributeBlob.length(CFDataGetLength(derBlob));
+ attributeBlob.copy(CFDataGetBytePtr(derBlob), CFDataGetLength(derBlob));
+}
+
+void ItemImpl::computeDigest(CssmOwnedData &sha2) {
+ auto_ptr<DbAttributes> dbAttributes(getCurrentAttributes());
+ ItemImpl::computeDigestFromDictionary(sha2, dbAttributes.get());
+}
+
+void ItemImpl::computeDigestFromDictionary(CssmOwnedData &sha2, DbAttributes* dbAttributes) {
+ try{
+ CssmAutoData attributeBlob(Allocator::standard());
+ encodeAttributesFromDictionary(attributeBlob, dbAttributes);
+
+ sha2.length(CC_SHA256_DIGEST_LENGTH);
+ CC_SHA256(attributeBlob.get().data(), static_cast<CC_LONG>(attributeBlob.get().length()), sha2);
+ secinfo("integrity", "finished: %s", sha2.get().toHex().c_str());
+ } catch (MacOSError mose) {
+ secnotice("integrity", "MacOSError: %d", (int)mose.osStatus());
+ } catch (...) {
+ secnotice("integrity", "unknown exception");
+ }
+}
+
+void ItemImpl::addIntegrity(Access &access, bool force) {
+ if(!force && (!mKeychain || !mKeychain->hasIntegrityProtection())) {
+ secinfo("integrity", "skipping integrity add due to keychain version\n");
+ return;
+ }
+
+ ACL * acl = NULL;
+ CssmAutoData digest(Allocator::standard());
+ computeDigest(digest);
+
+ // First, check if this already has an integrity tag
+ vector<ACL *> acls;
+ access.findSpecificAclsForRight(CSSM_ACL_AUTHORIZATION_INTEGRITY, acls);
+
+ if(acls.size() >= 1) {
+ // Use the existing ACL
+ acl = acls[0];
+ secinfo("integrity", "previous integrity acl exists; setting integrity");
+ acl->setIntegrity(digest.get());
+
+ // Delete all extra ACLs
+ for(int i = 1; i < acls.size(); i++) {
+ secnotice("integrity", "extra integrity acls exist; removing %d",i);
+ acls[i]->remove();
+ }
+ } else if(acls.size() == 0) {
+ // Make a new ACL
+ secnotice("integrity", "no previous integrity acl exists; making a new one");
+ acl = new ACL(digest.get());
+ access.add(acl);
+ }
+}
+
+ void ItemImpl::setIntegrity(bool force) {
+ if(!force && (!mKeychain || !mKeychain->hasIntegrityProtection())) {
+ secnotice("integrity", "skipping integrity set due to keychain version");
+ return;
+ }
+
+ // For Items, only passwords should have integrity
+ if(!(recordType() == CSSM_DL_DB_RECORD_GENERIC_PASSWORD || recordType() == CSSM_DL_DB_RECORD_INTERNET_PASSWORD)) {
+ return;
+ }
+
+ // If we're not on an SSDb, we shouldn't have integrity
+ Db db(mKeychain->database());
+ if (!useSecureStorage(db)) {
+ return;
+ }
+
+ setIntegrity(*group(), force);
+ }
+
+void ItemImpl::setIntegrity(AclBearer &bearer, bool force) {
+ if(!force && (!mKeychain || !mKeychain->hasIntegrityProtection())) {
+ secnotice("integrity", "skipping integrity acl set due to keychain version");
+ return;
+ }
+
+ SecPointer<Access> access = new Access(bearer);
+
+ access->removeAclsForRight(CSSM_ACL_AUTHORIZATION_PARTITION_ID);
+ addIntegrity(*access, force);
+ access->setAccess(bearer, true);
+}
+
+void ItemImpl::removeIntegrity(const AccessCredentials *cred) {
+ removeIntegrity(*group(), cred);
+}
+
+void ItemImpl::removeIntegrity(AclBearer &bearer, const AccessCredentials *cred) {
+ SecPointer<Access> access = new Access(bearer);
+ vector<ACL *> acls;
+
+ access->findSpecificAclsForRight(CSSM_ACL_AUTHORIZATION_INTEGRITY, acls);
+ for(int i = 0; i < acls.size(); i++) {
+ acls[i]->remove();
+ }
+
+ access->findSpecificAclsForRight(CSSM_ACL_AUTHORIZATION_PARTITION_ID, acls);
+ for(int i = 0; i < acls.size(); i++) {
+ acls[i]->remove();
+ }
+
+ access->editAccess(bearer, true, cred);
+}
+
+bool ItemImpl::checkIntegrity() {
+ // Note: subclasses are responsible for checking themselves.
+
+ // If we don't have a keychain yet, we don't have any group. Return true?
+ if(!isPersistent()) {
+ secnotice("integrity", "no keychain, integrity is valid?");
+ return true;
+ }
+
+ if(!mKeychain || !mKeychain->hasIntegrityProtection()) {
+ secinfo("integrity", "skipping integrity check due to keychain version");
+ return true;
+ }
+
+ // Collect our SSGroup, if it exists.
+ dbUniqueRecord();
+ SSGroup ssGroup = group();
+ if(ssGroup) {
+ return checkIntegrity(*ssGroup);
+ }
+
+ // If we don't have an SSGroup, we can't be invalid. return true.
+ return true;
+}
+
+bool ItemImpl::checkIntegrity(AclBearer& aclBearer) {
+ if(!mKeychain || !mKeychain->hasIntegrityProtection()) {
+ secinfo("integrity", "skipping integrity check due to keychain version");
+ return true;
+ }
+
+ auto_ptr<DbAttributes> dbAttributes(getCurrentAttributes());
+ return checkIntegrityFromDictionary(aclBearer, dbAttributes.get());
+}
+
+bool ItemImpl::checkIntegrityFromDictionary(AclBearer& aclBearer, DbAttributes* dbAttributes) {
+ try {
+ AutoAclEntryInfoList aclInfos;
+ aclBearer.getAcl(aclInfos, CSSM_APPLE_ACL_TAG_INTEGRITY);
+
+ // We should only expect there to be one integrity tag. If there's not,
+ // take the first one and ignore the rest. We should probably attempt delete
+ // them.
+
+ AclEntryInfo &info = aclInfos.at(0);
+ auto_ptr<ACL> acl(new ACL(info, Allocator::standard()));