Credstore changes for Android 12
- Add Credential.proveOwership()
- Add Credential.deleteWithChallenge()
- Add Credential.updateCredential()
- Add Credential.storeStaticAuthenticationDataWithExpirationDate()
- Store this on disk. For entries stored without this parameter
assume they never expire.
- Add allowUsingExpiredKeys to Credential.selectAuthKey() and
Credential.getEntries()
- Unless set to true, never select an expired key
- Introduce ERROR_NOT_SUPPORTED and return this if HAL does not
support operation
Bug: 170146643
Test: atest android.security.identity.cts
Change-Id: Ic5dafc6498c9c59b82942def9d348d974f008589
diff --git a/identity/Credential.cpp b/identity/Credential.cpp
index 28ba752..4a2bae1 100644
--- a/identity/Credential.cpp
+++ b/identity/Credential.cpp
@@ -36,6 +36,7 @@
#include "Credential.h"
#include "CredentialData.h"
#include "Util.h"
+#include "WritableCredential.h"
namespace android {
namespace security {
@@ -47,6 +48,8 @@
using android::security::keystore::IKeystoreService;
+using ::android::hardware::identity::IWritableIdentityCredential;
+
using ::android::hardware::identity::support::ecKeyPairGetPkcs12;
using ::android::hardware::identity::support::ecKeyPairGetPrivateKey;
using ::android::hardware::identity::support::ecKeyPairGetPublicKey;
@@ -58,25 +61,26 @@
using AidlVerificationToken = android::hardware::keymaster::VerificationToken;
Credential::Credential(CipherSuite cipherSuite, const std::string& dataPath,
- const std::string& credentialName)
- : cipherSuite_(cipherSuite), dataPath_(dataPath), credentialName_(credentialName) {}
+ const std::string& credentialName, uid_t callingUid,
+ HardwareInformation hwInfo, sp<IIdentityCredentialStore> halStoreBinder,
+ int halApiVersion)
+ : cipherSuite_(cipherSuite), dataPath_(dataPath), credentialName_(credentialName),
+ callingUid_(callingUid), hwInfo_(std::move(hwInfo)), halStoreBinder_(halStoreBinder),
+ halApiVersion_(halApiVersion) {}
Credential::~Credential() {}
-Status Credential::loadCredential(sp<IIdentityCredentialStore> halStoreBinder) {
- uid_t callingUid = android::IPCThreadState::self()->getCallingUid();
- sp<CredentialData> data = new CredentialData(dataPath_, callingUid, credentialName_);
+Status Credential::ensureOrReplaceHalBinder() {
+ sp<CredentialData> data = new CredentialData(dataPath_, callingUid_, credentialName_);
if (!data->loadFromDisk()) {
LOG(ERROR) << "Error loading data for credential";
return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC,
"Error loading data for credential");
}
- data_ = data;
-
sp<IIdentityCredential> halBinder;
Status status =
- halStoreBinder->getCredential(cipherSuite_, data_->getCredentialData(), &halBinder);
+ halStoreBinder_->getCredential(cipherSuite_, data->getCredentialData(), &halBinder);
if (!status.isOk() && status.exceptionCode() == binder::Status::EX_SERVICE_SPECIFIC) {
int code = status.serviceSpecificErrorCode();
if (code == IIdentityCredentialStore::STATUS_CIPHER_SUITE_NOT_SUPPORTED) {
@@ -87,21 +91,33 @@
LOG(ERROR) << "Error getting HAL binder";
return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC);
}
-
halBinder_ = halBinder;
return Status::ok();
}
Status Credential::getCredentialKeyCertificateChain(std::vector<uint8_t>* _aidl_return) {
- *_aidl_return = data_->getAttestationCertificate();
+ sp<CredentialData> data = new CredentialData(dataPath_, callingUid_, credentialName_);
+ if (!data->loadFromDisk()) {
+ LOG(ERROR) << "Error loading data for credential";
+ return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC,
+ "Error loading data for credential");
+ }
+ *_aidl_return = data->getAttestationCertificate();
return Status::ok();
}
// Returns operation handle
-Status Credential::selectAuthKey(bool allowUsingExhaustedKeys, int64_t* _aidl_return) {
+Status Credential::selectAuthKey(bool allowUsingExhaustedKeys, bool allowUsingExpiredKeys,
+ int64_t* _aidl_return) {
+ sp<CredentialData> data = new CredentialData(dataPath_, callingUid_, credentialName_);
+ if (!data->loadFromDisk()) {
+ LOG(ERROR) << "Error loading data for credential";
+ return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC,
+ "Error loading data for credential");
+ }
- selectedAuthKey_ = data_->selectAuthKey(allowUsingExhaustedKeys);
+ selectedAuthKey_ = data->selectAuthKey(allowUsingExhaustedKeys, allowUsingExpiredKeys);
if (selectedAuthKey_ == nullptr) {
return Status::fromServiceSpecificError(
ICredentialStore::ERROR_NO_AUTHENTICATION_KEY_AVAILABLE,
@@ -174,16 +190,23 @@
const vector<RequestNamespaceParcel>& requestNamespaces,
const vector<uint8_t>& sessionTranscript,
const vector<uint8_t>& readerSignature, bool allowUsingExhaustedKeys,
- GetEntriesResultParcel* _aidl_return) {
+ bool allowUsingExpiredKeys, GetEntriesResultParcel* _aidl_return) {
GetEntriesResultParcel ret;
+ sp<CredentialData> data = new CredentialData(dataPath_, callingUid_, credentialName_);
+ if (!data->loadFromDisk()) {
+ LOG(ERROR) << "Error loading data for credential";
+ return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC,
+ "Error loading data for credential");
+ }
+
// Calculate requestCounts ahead of time and be careful not to include
// elements that don't exist.
//
// Also go through and figure out which access control profiles to include
// in the startRetrieval() call.
vector<int32_t> requestCounts;
- const vector<SecureAccessControlProfile>& allProfiles = data_->getSecureAccessControlProfiles();
+ const vector<SecureAccessControlProfile>& allProfiles = data->getSecureAccessControlProfiles();
// We don't support ACP identifiers which isn't in the range 0 to 31. This
// guarantee exists so it's feasible to implement the TA part of an Identity
@@ -202,13 +225,13 @@
for (const RequestNamespaceParcel& rns : requestNamespaces) {
size_t numEntriesInNsToRequest = 0;
for (const RequestEntryParcel& rep : rns.entries) {
- if (data_->hasEntryData(rns.namespaceName, rep.name)) {
+ if (data->hasEntryData(rns.namespaceName, rep.name)) {
numEntriesInNsToRequest++;
}
- optional<EntryData> data = data_->getEntryData(rns.namespaceName, rep.name);
- if (data) {
- for (int32_t id : data.value().accessControlProfileIds) {
+ optional<EntryData> eData = data->getEntryData(rns.namespaceName, rep.name);
+ if (eData) {
+ for (int32_t id : eData.value().accessControlProfileIds) {
if (id < 0 || id >= 32) {
LOG(ERROR) << "Invalid accessControlProfileId " << id << " for "
<< rns.namespaceName << ": " << rep.name;
@@ -282,7 +305,7 @@
if (userAuthNeeded) {
vector<uint8_t> authTokenBytes;
vector<uint8_t> verificationTokenBytes;
- if (!getTokensFromKeystore(selectedChallenge_, data_->getSecureUserId(),
+ if (!getTokensFromKeystore(selectedChallenge_, data->getSecureUserId(),
authTokenMaxAgeMillis, authTokenBytes, verificationTokenBytes)) {
LOG(ERROR) << "Error getting tokens from keystore";
return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC,
@@ -331,7 +354,7 @@
const AuthKeyData* authKey = selectedAuthKey_;
if (sessionTranscript.size() > 0) {
if (authKey == nullptr) {
- authKey = data_->selectAuthKey(allowUsingExhaustedKeys);
+ authKey = data->selectAuthKey(allowUsingExhaustedKeys, allowUsingExpiredKeys);
if (authKey == nullptr) {
return Status::fromServiceSpecificError(
ICredentialStore::ERROR_NO_AUTHENTICATION_KEY_AVAILABLE,
@@ -351,7 +374,7 @@
RequestNamespace ns;
ns.namespaceName = rns.namespaceName;
for (const RequestEntryParcel& rep : rns.entries) {
- optional<EntryData> entryData = data_->getEntryData(rns.namespaceName, rep.name);
+ optional<EntryData> entryData = data->getEntryData(rns.namespaceName, rep.name);
if (entryData) {
RequestDataItem di;
di.name = rep.name;
@@ -406,16 +429,16 @@
ResultEntryParcel resultEntryParcel;
resultEntryParcel.name = rep.name;
- optional<EntryData> data = data_->getEntryData(rns.namespaceName, rep.name);
- if (!data) {
+ optional<EntryData> eData = data->getEntryData(rns.namespaceName, rep.name);
+ if (!eData) {
resultEntryParcel.status = STATUS_NO_SUCH_ENTRY;
resultNamespaceParcel.entries.push_back(resultEntryParcel);
continue;
}
status =
- halBinder_->startRetrieveEntryValue(rns.namespaceName, rep.name, data.value().size,
- data.value().accessControlProfileIds);
+ halBinder_->startRetrieveEntryValue(rns.namespaceName, rep.name, eData.value().size,
+ eData.value().accessControlProfileIds);
if (!status.isOk() && status.exceptionCode() == binder::Status::EX_SERVICE_SPECIFIC) {
int code = status.serviceSpecificErrorCode();
if (code == IIdentityCredentialStore::STATUS_USER_AUTHENTICATION_FAILED) {
@@ -441,7 +464,7 @@
}
vector<uint8_t> value;
- for (const auto& encryptedChunk : data.value().encryptedChunks) {
+ for (const auto& encryptedChunk : eData.value().encryptedChunks) {
vector<uint8_t> chunk;
status = halBinder_->retrieveEntryValue(encryptedChunk, &chunk);
if (!status.isOk()) {
@@ -467,7 +490,7 @@
// Ensure useCount is updated on disk.
if (authKey != nullptr) {
- if (!data_->saveToDisk()) {
+ if (!data->saveToDisk()) {
return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC,
"Error saving data");
}
@@ -479,11 +502,19 @@
Status Credential::deleteCredential(vector<uint8_t>* _aidl_return) {
vector<uint8_t> proofOfDeletionSignature;
+
+ sp<CredentialData> data = new CredentialData(dataPath_, callingUid_, credentialName_);
+ if (!data->loadFromDisk()) {
+ LOG(ERROR) << "Error loading data for credential";
+ return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC,
+ "Error loading data for credential");
+ }
+
Status status = halBinder_->deleteCredential(&proofOfDeletionSignature);
if (!status.isOk()) {
return halStatusToGenericError(status);
}
- if (!data_->deleteCredential()) {
+ if (!data->deleteCredential()) {
return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC,
"Error deleting credential data on disk");
}
@@ -491,6 +522,47 @@
return Status::ok();
}
+Status Credential::deleteWithChallenge(const vector<uint8_t>& challenge,
+ vector<uint8_t>* _aidl_return) {
+ if (halApiVersion_ < 3) {
+ return Status::fromServiceSpecificError(ICredentialStore::ERROR_NOT_SUPPORTED,
+ "Not implemented by HAL");
+ }
+ vector<uint8_t> proofOfDeletionSignature;
+
+ sp<CredentialData> data = new CredentialData(dataPath_, callingUid_, credentialName_);
+ if (!data->loadFromDisk()) {
+ LOG(ERROR) << "Error loading data for credential";
+ return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC,
+ "Error loading data for credential");
+ }
+
+ Status status = halBinder_->deleteCredentialWithChallenge(challenge, &proofOfDeletionSignature);
+ if (!status.isOk()) {
+ return halStatusToGenericError(status);
+ }
+ if (!data->deleteCredential()) {
+ return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC,
+ "Error deleting credential data on disk");
+ }
+ *_aidl_return = proofOfDeletionSignature;
+ return Status::ok();
+}
+
+Status Credential::proveOwnership(const vector<uint8_t>& challenge, vector<uint8_t>* _aidl_return) {
+ if (halApiVersion_ < 3) {
+ return Status::fromServiceSpecificError(ICredentialStore::ERROR_NOT_SUPPORTED,
+ "Not implemented by HAL");
+ }
+ vector<uint8_t> proofOfOwnershipSignature;
+ Status status = halBinder_->proveOwnership(challenge, &proofOfOwnershipSignature);
+ if (!status.isOk()) {
+ return halStatusToGenericError(status);
+ }
+ *_aidl_return = proofOfOwnershipSignature;
+ return Status::ok();
+}
+
Status Credential::createEphemeralKeyPair(vector<uint8_t>* _aidl_return) {
vector<uint8_t> keyPair;
Status status = halBinder_->createEphemeralKeyPair(&keyPair);
@@ -522,8 +594,14 @@
}
Status Credential::setAvailableAuthenticationKeys(int32_t keyCount, int32_t maxUsesPerKey) {
- data_->setAvailableAuthenticationKeys(keyCount, maxUsesPerKey);
- if (!data_->saveToDisk()) {
+ sp<CredentialData> data = new CredentialData(dataPath_, callingUid_, credentialName_);
+ if (!data->loadFromDisk()) {
+ LOG(ERROR) << "Error loading data for credential";
+ return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC,
+ "Error loading data for credential");
+ }
+ data->setAvailableAuthenticationKeys(keyCount, maxUsesPerKey);
+ if (!data->saveToDisk()) {
return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC,
"Error saving data");
}
@@ -531,8 +609,14 @@
}
Status Credential::getAuthKeysNeedingCertification(vector<AuthKeyParcel>* _aidl_return) {
+ sp<CredentialData> data = new CredentialData(dataPath_, callingUid_, credentialName_);
+ if (!data->loadFromDisk()) {
+ LOG(ERROR) << "Error loading data for credential";
+ return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC,
+ "Error loading data for credential");
+ }
optional<vector<vector<uint8_t>>> keysNeedingCert =
- data_->getAuthKeysNeedingCertification(halBinder_);
+ data->getAuthKeysNeedingCertification(halBinder_);
if (!keysNeedingCert) {
return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC,
"Error getting auth keys neededing certification");
@@ -543,7 +627,7 @@
authKeyParcel.x509cert = key;
authKeyParcels.push_back(authKeyParcel);
}
- if (!data_->saveToDisk()) {
+ if (!data->saveToDisk()) {
return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC,
"Error saving data");
}
@@ -553,13 +637,48 @@
Status Credential::storeStaticAuthenticationData(const AuthKeyParcel& authenticationKey,
const vector<uint8_t>& staticAuthData) {
- if (!data_->storeStaticAuthenticationData(authenticationKey.x509cert, staticAuthData)) {
+ sp<CredentialData> data = new CredentialData(dataPath_, callingUid_, credentialName_);
+ if (!data->loadFromDisk()) {
+ LOG(ERROR) << "Error loading data for credential";
+ return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC,
+ "Error loading data for credential");
+ }
+ if (!data->storeStaticAuthenticationData(authenticationKey.x509cert,
+ std::numeric_limits<int64_t>::max(), staticAuthData)) {
return Status::fromServiceSpecificError(
ICredentialStore::ERROR_AUTHENTICATION_KEY_NOT_FOUND,
"Error finding authentication key to store static "
"authentication data for");
}
- if (!data_->saveToDisk()) {
+ if (!data->saveToDisk()) {
+ return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC,
+ "Error saving data");
+ }
+ return Status::ok();
+}
+
+Status
+Credential::storeStaticAuthenticationDataWithExpiration(const AuthKeyParcel& authenticationKey,
+ int64_t expirationDateMillisSinceEpoch,
+ const vector<uint8_t>& staticAuthData) {
+ if (halApiVersion_ < 3) {
+ return Status::fromServiceSpecificError(ICredentialStore::ERROR_NOT_SUPPORTED,
+ "Not implemented by HAL");
+ }
+ sp<CredentialData> data = new CredentialData(dataPath_, callingUid_, credentialName_);
+ if (!data->loadFromDisk()) {
+ LOG(ERROR) << "Error loading data for credential";
+ return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC,
+ "Error loading data for credential");
+ }
+ if (!data->storeStaticAuthenticationData(authenticationKey.x509cert,
+ expirationDateMillisSinceEpoch, staticAuthData)) {
+ return Status::fromServiceSpecificError(
+ ICredentialStore::ERROR_AUTHENTICATION_KEY_NOT_FOUND,
+ "Error finding authentication key to store static "
+ "authentication data for");
+ }
+ if (!data->saveToDisk()) {
return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC,
"Error saving data");
}
@@ -567,7 +686,13 @@
}
Status Credential::getAuthenticationDataUsageCount(vector<int32_t>* _aidl_return) {
- const vector<AuthKeyData>& authKeyDatas = data_->getAuthKeyDatas();
+ sp<CredentialData> data = new CredentialData(dataPath_, callingUid_, credentialName_);
+ if (!data->loadFromDisk()) {
+ LOG(ERROR) << "Error loading data for credential";
+ return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC,
+ "Error loading data for credential");
+ }
+ const vector<AuthKeyData>& authKeyDatas = data->getAuthKeyDatas();
vector<int32_t> ret;
for (const AuthKeyData& authKeyData : authKeyDatas) {
ret.push_back(authKeyData.useCount);
@@ -576,6 +701,80 @@
return Status::ok();
}
+optional<string> extractDocType(const vector<uint8_t>& credentialData) {
+ auto [item, _ /* newPos */, message] = cppbor::parse(credentialData);
+ if (item == nullptr) {
+ LOG(ERROR) << "CredentialData is not valid CBOR: " << message;
+ return {};
+ }
+ const cppbor::Array* array = item->asArray();
+ if (array == nullptr || array->size() < 1) {
+ LOG(ERROR) << "CredentialData array with at least one element";
+ return {};
+ }
+ const cppbor::Tstr* tstr = ((*array)[0])->asTstr();
+ if (tstr == nullptr) {
+ LOG(ERROR) << "First item in CredentialData is not a string";
+ return {};
+ }
+ return tstr->value();
+}
+
+Status Credential::update(sp<IWritableCredential>* _aidl_return) {
+ if (halApiVersion_ < 3) {
+ return Status::fromServiceSpecificError(ICredentialStore::ERROR_NOT_SUPPORTED,
+ "Not implemented by HAL");
+ }
+ sp<CredentialData> data = new CredentialData(dataPath_, callingUid_, credentialName_);
+ if (!data->loadFromDisk()) {
+ LOG(ERROR) << "Error loading data for credential";
+ return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC,
+ "Error loading data for credential");
+ }
+
+ sp<IWritableIdentityCredential> halWritableCredential;
+ Status status = halBinder_->updateCredential(&halWritableCredential);
+ if (!status.isOk()) {
+ return halStatusToGenericError(status);
+ }
+
+ optional<string> docType = extractDocType(data->getCredentialData());
+ if (!docType) {
+ return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC,
+ "Unable to extract DocType from CredentialData");
+ }
+
+ // NOTE: The caller is expected to call WritableCredential::personalize() which will
+ // write brand new data to disk, specifically it will overwrite any data already
+ // have _including_ authentication keys.
+ //
+ // It is because of this we need to set the CredentialKey certificate chain,
+ // keyCount, and maxUsesPerKey below.
+ sp<WritableCredential> writableCredential =
+ new WritableCredential(dataPath_, credentialName_, docType.value(), true, hwInfo_,
+ halWritableCredential, halApiVersion_);
+
+ writableCredential->setAttestationCertificate(data->getAttestationCertificate());
+ auto [keyCount, maxUsesPerKey] = data->getAvailableAuthenticationKeys();
+ writableCredential->setAvailableAuthenticationKeys(keyCount, maxUsesPerKey);
+
+ // Because its data has changed, we need to reconnect to the HAL when the
+ // credential has been updated... otherwise the remote object will have
+ // stale data for future calls (e.g. getAuthKeysNeedingCertification().
+ //
+ // The joys and pitfalls of mutable objects...
+ //
+ writableCredential->setCredentialUpdatedCallback([this] {
+ Status status = this->ensureOrReplaceHalBinder();
+ if (!status.isOk()) {
+ LOG(ERROR) << "Error loading credential";
+ }
+ });
+
+ *_aidl_return = writableCredential;
+ return Status::ok();
+}
+
} // namespace identity
} // namespace security
} // namespace android