/* * Copyright (c) 2019, The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #define LOG_TAG "credstore" #include #include #include #include #include #include #include #include #include #include #include #include "CredentialData.h" #include "Util.h" namespace android { namespace security { namespace identity { using std::optional; string CredentialData::calculateCredentialFileName(const string& dataPath, uid_t ownerUid, const string& name) { return android::base::StringPrintf( "%s/%d-%s", dataPath.c_str(), (int)ownerUid, android::hardware::identity::support::encodeHex(name).c_str()); } CredentialData::CredentialData(const string& dataPath, uid_t ownerUid, const string& name) : dataPath_(dataPath), ownerUid_(ownerUid), name_(name), secureUserId_(0) { fileName_ = calculateCredentialFileName(dataPath_, ownerUid_, name_); } void CredentialData::setSecureUserId(int64_t secureUserId) { secureUserId_ = secureUserId; } void CredentialData::setCredentialData(const vector& credentialData) { credentialData_ = credentialData; } void CredentialData::setAttestationCertificate(const vector& attestationCertificate) { attestationCertificate_ = attestationCertificate; } void CredentialData::addSecureAccessControlProfile( const SecureAccessControlProfile& secureAccessControlProfile) { secureAccessControlProfiles_.push_back(secureAccessControlProfile); } void CredentialData::addEntryData(const string& namespaceName, const string& entryName, const EntryData& data) { idToEncryptedChunks_[namespaceName + ":" + entryName] = data; } bool CredentialData::saveToDisk() const { cppbor::Map map; map.add("secureUserId", secureUserId_); map.add("credentialData", credentialData_); map.add("attestationCertificate", attestationCertificate_); cppbor::Array sacpArray; for (const SecureAccessControlProfile& sacp : secureAccessControlProfiles_) { cppbor::Array array; array.add(sacp.id); array.add(sacp.readerCertificate.encodedCertificate); array.add(sacp.userAuthenticationRequired); array.add(sacp.timeoutMillis); array.add(sacp.secureUserId); vector mac = sacp.mac; array.add(mac); sacpArray.add(std::move(array)); } map.add("secureAccessControlProfiles", std::move(sacpArray)); cppbor::Map encryptedBlobsMap; for (auto const& [nsAndName, entryData] : idToEncryptedChunks_) { cppbor::Array encryptedChunkArray; for (const vector& encryptedChunk : entryData.encryptedChunks) { encryptedChunkArray.add(encryptedChunk); } cppbor::Array entryDataArray; entryDataArray.add(entryData.size); cppbor::Array idsArray; for (int32_t id : entryData.accessControlProfileIds) { idsArray.add(id); } entryDataArray.add(std::move(idsArray)); entryDataArray.add(std::move(encryptedChunkArray)); encryptedBlobsMap.add(nsAndName, std::move(entryDataArray)); } map.add("entryData", std::move(encryptedBlobsMap)); map.add("authKeyCount", keyCount_); map.add("maxUsesPerAuthKey", maxUsesPerKey_); cppbor::Array authKeyDatasArray; for (const AuthKeyData& data : authKeyDatas_) { cppbor::Array array; // Fields 0-6 was in the original version in Android 11 array.add(data.certificate); array.add(data.keyBlob); array.add(data.staticAuthenticationData); array.add(data.pendingCertificate); array.add(data.pendingKeyBlob); array.add(data.useCount); // Field 7 was added in Android 12 array.add(data.expirationDateMillisSinceEpoch); authKeyDatasArray.add(std::move(array)); } map.add("authKeyData", std::move(authKeyDatasArray)); vector credentialData = map.encode(); return fileSetContents(fileName_, credentialData); } optional parseSacp(const cppbor::Item& item) { const cppbor::Array* array = item.asArray(); if (array == nullptr || array->size() < 6) { LOG(ERROR) << "The SACP CBOR is not an array with at least six elements (size=" << (array != nullptr ? array->size() : -1) << ")"; return {}; } const cppbor::Int* itemId = ((*array)[0])->asInt(); const cppbor::Bstr* itemReaderCertificate = ((*array)[1])->asBstr(); const cppbor::Simple* simple = ((*array)[2])->asSimple(); const cppbor::Bool* itemUserAuthenticationRequired = (simple != nullptr ? (simple->asBool()) : nullptr); const cppbor::Int* itemTimeoutMillis = ((*array)[3])->asInt(); const cppbor::Int* itesecureUserId_ = ((*array)[4])->asInt(); const cppbor::Bstr* itemMac = ((*array)[5])->asBstr(); if (itemId == nullptr || itemReaderCertificate == nullptr || itemUserAuthenticationRequired == nullptr || itemTimeoutMillis == nullptr || itesecureUserId_ == nullptr || itemMac == nullptr) { LOG(ERROR) << "One or more items SACP array in CBOR is of wrong type"; return {}; } SecureAccessControlProfile sacp; sacp.id = itemId->value(); sacp.readerCertificate.encodedCertificate = itemReaderCertificate->value(); sacp.userAuthenticationRequired = itemUserAuthenticationRequired->value(); sacp.timeoutMillis = itemTimeoutMillis->value(); sacp.secureUserId = itesecureUserId_->value(); sacp.mac = itemMac->value(); return sacp; } optional parseAuthKeyData(const cppbor::Item& item) { const cppbor::Array* array = item.asArray(); if (array == nullptr || array->size() < 6) { LOG(ERROR) << "The AuthKeyData CBOR is not an array with at least six elements"; return {}; } const cppbor::Bstr* itemCertificate = ((*array)[0])->asBstr(); const cppbor::Bstr* itemKeyBlob = ((*array)[1])->asBstr(); const cppbor::Bstr* itemStaticAuthenticationData = ((*array)[2])->asBstr(); const cppbor::Bstr* itemPendingCertificate = ((*array)[3])->asBstr(); const cppbor::Bstr* itemPendingKeyBlob = ((*array)[4])->asBstr(); const cppbor::Int* itemUseCount = ((*array)[5])->asInt(); if (itemCertificate == nullptr || itemKeyBlob == nullptr || itemStaticAuthenticationData == nullptr || itemPendingCertificate == nullptr || itemPendingKeyBlob == nullptr || itemUseCount == nullptr) { LOG(ERROR) << "One or more items in AuthKeyData array in CBOR is of wrong type"; return {}; } // expirationDateMillisSinceEpoch was added as the 7th element for Android 12. If not // present, default to longest possible expiration date. int64_t expirationDateMillisSinceEpoch = INT64_MAX; if (array->size() >= 7) { const cppbor::Int* itemExpirationDateMillisSinceEpoch = ((*array)[6])->asInt(); expirationDateMillisSinceEpoch = itemExpirationDateMillisSinceEpoch->value(); } AuthKeyData authKeyData; authKeyData.certificate = itemCertificate->value(); authKeyData.keyBlob = itemKeyBlob->value(); authKeyData.expirationDateMillisSinceEpoch = expirationDateMillisSinceEpoch; authKeyData.staticAuthenticationData = itemStaticAuthenticationData->value(); authKeyData.pendingCertificate = itemPendingCertificate->value(); authKeyData.pendingKeyBlob = itemPendingKeyBlob->value(); authKeyData.useCount = itemUseCount->value(); return authKeyData; } vector parseAccessControlProfileIds(const cppbor::Item& item) { const cppbor::Array* array = item.asArray(); if (array == nullptr) { LOG(ERROR) << "The accessControlProfileIds member is not an array"; return {}; } vector accessControlProfileIds; for (size_t n = 0; n < array->size(); n++) { const cppbor::Int* itemInt = ((*array)[n])->asInt(); if (itemInt == nullptr) { LOG(ERROR) << "An item in the accessControlProfileIds array is not a bstr"; return {}; } accessControlProfileIds.push_back(itemInt->value()); } return accessControlProfileIds; } optional>> parseEncryptedChunks(const cppbor::Item& item) { const cppbor::Array* array = item.asArray(); if (array == nullptr) { LOG(ERROR) << "The encryptedChunks member is not an array"; return {}; } vector> encryptedChunks; for (size_t n = 0; n < array->size(); n++) { const cppbor::Bstr* itemBstr = ((*array)[n])->asBstr(); if (itemBstr == nullptr) { LOG(ERROR) << "An item in the encryptedChunks array is not a bstr"; return {}; } encryptedChunks.push_back(itemBstr->value()); } return encryptedChunks; } bool CredentialData::loadFromDisk() { // Reset all data. credentialData_.clear(); attestationCertificate_.clear(); secureAccessControlProfiles_.clear(); idToEncryptedChunks_.clear(); authKeyDatas_.clear(); keyCount_ = 0; maxUsesPerKey_ = 1; optional> data = fileGetContents(fileName_); if (!data) { LOG(ERROR) << "Error loading data"; return false; } auto [item, _ /* newPos */, message] = cppbor::parse(data.value()); if (item == nullptr) { LOG(ERROR) << "Data loaded from " << fileName_ << " is not valid CBOR: " << message; return false; } const cppbor::Map* map = item->asMap(); if (map == nullptr) { LOG(ERROR) << "Top-level item is not a map"; return false; } for (size_t n = 0; n < map->size(); n++) { auto& [keyItem, valueItem] = (*map)[n]; const cppbor::Tstr* tstr = keyItem->asTstr(); if (tstr == nullptr) { LOG(ERROR) << "Key item in top-level map is not a tstr"; return false; } const string& key = tstr->value(); if (key == "secureUserId") { const cppbor::Int* number = valueItem->asInt(); if (number == nullptr) { LOG(ERROR) << "Value for secureUserId is not a number"; return false; } secureUserId_ = number->value(); } else if (key == "credentialData") { const cppbor::Bstr* valueBstr = valueItem->asBstr(); if (valueBstr == nullptr) { LOG(ERROR) << "Value for credentialData is not a bstr"; return false; } credentialData_ = valueBstr->value(); } else if (key == "attestationCertificate") { const cppbor::Bstr* valueBstr = valueItem->asBstr(); if (valueBstr == nullptr) { LOG(ERROR) << "Value for attestationCertificate is not a bstr"; return false; } attestationCertificate_ = valueBstr->value(); } else if (key == "secureAccessControlProfiles") { const cppbor::Array* array = valueItem->asArray(); if (array == nullptr) { LOG(ERROR) << "Value for attestationCertificate is not an array"; return false; } for (size_t m = 0; m < array->size(); m++) { const std::unique_ptr& item = (*array)[m]; optional sacp = parseSacp(*item); if (!sacp) { LOG(ERROR) << "Error parsing SecureAccessControlProfile"; return false; } secureAccessControlProfiles_.push_back(sacp.value()); } } else if (key == "entryData") { const cppbor::Map* map = valueItem->asMap(); if (map == nullptr) { LOG(ERROR) << "Value for encryptedChunks is not an map"; return false; } for (size_t m = 0; m < map->size(); m++) { auto& [ecKeyItem, ecValueItem] = (*map)[m]; const cppbor::Tstr* ecTstr = ecKeyItem->asTstr(); if (ecTstr == nullptr) { LOG(ERROR) << "Key item in encryptedChunks map is not a tstr"; return false; } const string& ecId = ecTstr->value(); const cppbor::Array* ecEntryArrayItem = ecValueItem->asArray(); if (ecEntryArrayItem == nullptr || ecEntryArrayItem->size() < 3) { LOG(ERROR) << "Value item in encryptedChunks map is an array with at least two " "elements"; return false; } const cppbor::Int* ecEntrySizeItem = (*ecEntryArrayItem)[0]->asInt(); if (ecEntrySizeItem == nullptr) { LOG(ERROR) << "Entry size not a number"; return false; } uint64_t entrySize = ecEntrySizeItem->value(); optional> accessControlProfileIds = parseAccessControlProfileIds(*(*ecEntryArrayItem)[1]); if (!accessControlProfileIds) { LOG(ERROR) << "Error parsing access control profile ids"; return false; } optional>> encryptedChunks = parseEncryptedChunks(*(*ecEntryArrayItem)[2]); if (!encryptedChunks) { LOG(ERROR) << "Error parsing encrypted chunks"; return false; } EntryData data; data.size = entrySize; data.accessControlProfileIds = accessControlProfileIds.value(); data.encryptedChunks = encryptedChunks.value(); idToEncryptedChunks_[ecId] = data; } } else if (key == "authKeyData") { const cppbor::Array* array = valueItem->asArray(); if (array == nullptr) { LOG(ERROR) << "Value for authData is not an array"; return false; } for (size_t m = 0; m < array->size(); m++) { const std::unique_ptr& item = (*array)[m]; optional authKeyData = parseAuthKeyData(*item); if (!authKeyData) { LOG(ERROR) << "Error parsing AuthKeyData"; return false; } authKeyDatas_.push_back(authKeyData.value()); } } else if (key == "authKeyCount") { const cppbor::Int* number = valueItem->asInt(); if (number == nullptr) { LOG(ERROR) << "Value for authKeyCount is not a number"; return false; } keyCount_ = number->value(); } else if (key == "maxUsesPerAuthKey") { const cppbor::Int* number = valueItem->asInt(); if (number == nullptr) { LOG(ERROR) << "Value for maxUsesPerAuthKey is not a number"; return false; } maxUsesPerKey_ = number->value(); } } if (credentialData_.size() == 0) { LOG(ERROR) << "Missing credentialData"; return false; } if (attestationCertificate_.size() == 0) { LOG(ERROR) << "Missing attestationCertificate"; return false; } if (size_t(keyCount_) != authKeyDatas_.size()) { LOG(ERROR) << "keyCount_=" << keyCount_ << " != authKeyDatas_.size()=" << authKeyDatas_.size(); return false; } return true; } const vector& CredentialData::getCredentialData() const { return credentialData_; } int64_t CredentialData::getSecureUserId() { return secureUserId_; } const vector& CredentialData::getAttestationCertificate() const { return attestationCertificate_; } const vector& CredentialData::getSecureAccessControlProfiles() const { return secureAccessControlProfiles_; } bool CredentialData::hasEntryData(const string& namespaceName, const string& entryName) const { string id = namespaceName + ":" + entryName; auto iter = idToEncryptedChunks_.find(id); if (iter == idToEncryptedChunks_.end()) { return false; } return true; } optional CredentialData::getEntryData(const string& namespaceName, const string& entryName) const { string id = namespaceName + ":" + entryName; auto iter = idToEncryptedChunks_.find(id); if (iter == idToEncryptedChunks_.end()) { return {}; } return iter->second; } bool CredentialData::deleteCredential() { if (unlink(fileName_.c_str()) != 0) { PLOG(ERROR) << "Error deleting " << fileName_; return false; } return true; } optional CredentialData::credentialExists(const string& dataPath, uid_t ownerUid, const string& name) { struct stat statbuf; string filename = calculateCredentialFileName(dataPath, ownerUid, name); if (stat(filename.c_str(), &statbuf) != 0) { if (errno == ENOENT) { return false; } PLOG(ERROR) << "Error getting information about " << filename; return {}; } return true; } // --- void CredentialData::setAvailableAuthenticationKeys(int keyCount, int maxUsesPerKey) { keyCount_ = keyCount; maxUsesPerKey_ = maxUsesPerKey; // If growing the number of auth keys (prevKeyCount < keyCount_ case) we'll add // new AuthKeyData structs to |authKeyDatas_| and each struct will have empty |certificate| // and |pendingCertificate| fields. Those will be filled out when the // getAuthKeysNeedingCertification() is called. // // If shrinking, we'll just delete the AuthKeyData structs at the end. There's nothing // else to do, the HAL doesn't need to know we're nuking these authentication keys. // // Therefore, in either case it's as simple as just resizing the vector. authKeyDatas_.resize(keyCount_); } const vector& CredentialData::getAuthKeyDatas() const { return authKeyDatas_; } pair CredentialData::getAvailableAuthenticationKeys() { return std::make_pair(keyCount_, maxUsesPerKey_); } AuthKeyData* CredentialData::findAuthKey_(bool allowUsingExhaustedKeys, bool allowUsingExpiredKeys) { AuthKeyData* candidate = nullptr; int64_t nowMilliSeconds = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()) * 1000; int n = 0; for (AuthKeyData& data : authKeyDatas_) { if (nowMilliSeconds > data.expirationDateMillisSinceEpoch) { if (!allowUsingExpiredKeys) { continue; } } if (data.certificate.size() != 0) { // Not expired, include in normal check if (candidate == nullptr || data.useCount < candidate->useCount) { candidate = &data; } } n++; } if (candidate == nullptr) { return nullptr; } if (candidate->useCount >= maxUsesPerKey_ && !allowUsingExhaustedKeys) { return nullptr; } return candidate; } const AuthKeyData* CredentialData::selectAuthKey(bool allowUsingExhaustedKeys, bool allowUsingExpiredKeys) { AuthKeyData* candidate; // First try to find a un-expired key.. candidate = findAuthKey_(allowUsingExhaustedKeys, false); if (candidate == nullptr) { // That didn't work, there are no un-expired keys and we don't allow using expired keys. if (!allowUsingExpiredKeys) { return nullptr; } // See if there's an expired key then... candidate = findAuthKey_(allowUsingExhaustedKeys, true); if (candidate == nullptr) { return nullptr; } } candidate->useCount += 1; return candidate; } optional>> CredentialData::getAuthKeysNeedingCertification(const sp& halBinder) { vector> keysNeedingCert; int64_t nowMilliSeconds = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()) * 1000; for (AuthKeyData& data : authKeyDatas_) { bool keyExceedUseCount = (data.useCount >= maxUsesPerKey_); bool keyBeyondExpirationDate = (nowMilliSeconds > data.expirationDateMillisSinceEpoch); bool newKeyNeeded = (data.certificate.size() == 0) || keyExceedUseCount || keyBeyondExpirationDate; bool certificationPending = (data.pendingCertificate.size() > 0); if (newKeyNeeded && !certificationPending) { vector signingKeyBlob; Certificate signingKeyCertificate; if (!halBinder->generateSigningKeyPair(&signingKeyBlob, &signingKeyCertificate) .isOk()) { LOG(ERROR) << "Error generating signing key-pair"; return {}; } data.pendingCertificate = signingKeyCertificate.encodedCertificate; data.pendingKeyBlob = signingKeyBlob; certificationPending = true; } if (certificationPending) { keysNeedingCert.push_back(data.pendingCertificate); } } return keysNeedingCert; } bool CredentialData::storeStaticAuthenticationData(const vector& authenticationKey, int64_t expirationDateMillisSinceEpoch, const vector& staticAuthData) { for (AuthKeyData& data : authKeyDatas_) { if (data.pendingCertificate == authenticationKey) { data.certificate = data.pendingCertificate; data.keyBlob = data.pendingKeyBlob; data.expirationDateMillisSinceEpoch = expirationDateMillisSinceEpoch; data.staticAuthenticationData = staticAuthData; data.pendingCertificate.clear(); data.pendingKeyBlob.clear(); data.useCount = 0; return true; } } return false; } } // namespace identity } // namespace security } // namespace android