platform_system_security/identity/CredentialData.cpp
David Zeuthen 045a2c87e0 identity: Add multi-document presentation support.
Also fix a bug so the same AuthKey is used for several getEntries()
calls on a credential. This matches the behavior in the Jetpack.

Bug: 197965513
Test: New CTS tests and new screen in CtsVerifier
Change-Id: I344f44b5655f0977ee650b518ce669e3c8a7b47a
2022-01-10 17:38:04 -05:00

620 lines
23 KiB
C++

/*
* 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 <chrono>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <android-base/logging.h>
#include <android-base/stringprintf.h>
#include <cppbor.h>
#include <cppbor_parse.h>
#include <android/hardware/identity/support/IdentityCredentialSupport.h>
#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<uint8_t>& credentialData) {
credentialData_ = credentialData;
}
void CredentialData::setAttestationCertificate(const vector<uint8_t>& 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<uint8_t> 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<uint8_t>& 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<uint8_t> credentialData = map.encode();
return fileSetContents(fileName_, credentialData);
}
optional<SecureAccessControlProfile> 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<AuthKeyData> 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<int32_t> 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<int32_t> 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<vector<vector<uint8_t>>> 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<vector<uint8_t>> 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<vector<uint8_t>> 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<cppbor::Item>& item = (*array)[m];
optional<SecureAccessControlProfile> 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<vector<int32_t>> accessControlProfileIds =
parseAccessControlProfileIds(*(*ecEntryArrayItem)[1]);
if (!accessControlProfileIds) {
LOG(ERROR) << "Error parsing access control profile ids";
return false;
}
optional<vector<vector<uint8_t>>> 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<cppbor::Item>& item = (*array)[m];
optional<AuthKeyData> 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<uint8_t>& CredentialData::getCredentialData() const {
return credentialData_;
}
int64_t CredentialData::getSecureUserId() {
return secureUserId_;
}
const vector<uint8_t>& CredentialData::getAttestationCertificate() const {
return attestationCertificate_;
}
const vector<SecureAccessControlProfile>& 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<EntryData> 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<bool> 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<AuthKeyData>& CredentialData::getAuthKeyDatas() const {
return authKeyDatas_;
}
pair<int /* keyCount */, int /*maxUsersPerKey */> 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,
bool incrementUsageCount) {
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;
}
}
if (incrementUsageCount) {
candidate->useCount += 1;
}
return candidate;
}
optional<vector<vector<uint8_t>>>
CredentialData::getAuthKeysNeedingCertification(const sp<IIdentityCredential>& halBinder) {
vector<vector<uint8_t>> 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<uint8_t> 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<uint8_t>& authenticationKey,
int64_t expirationDateMillisSinceEpoch,
const vector<uint8_t>& 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