diff options
author | David Zeuthen <zeuthen@google.com> | 2020-10-16 11:50:13 -0400 |
---|---|---|
committer | David Zeuthen <zeuthen@google.com> | 2021-01-22 18:37:03 -0500 |
commit | 472e6c8e18c800b0b650bd8697e17ce65c1f3608 (patch) | |
tree | 471569d2a83c6aa0bbc43328f05d0e2cd51ee28b /identity | |
parent | 507171614b09405ec1660ed7726d6869745b128c (diff) | |
download | security-472e6c8e18c800b0b650bd8697e17ce65c1f3608.tar.gz |
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
Diffstat (limited to 'identity')
-rw-r--r-- | identity/Android.bp | 5 | ||||
-rw-r--r-- | identity/Credential.cpp | 269 | ||||
-rw-r--r-- | identity/Credential.h | 29 | ||||
-rw-r--r-- | identity/CredentialData.cpp | 64 | ||||
-rw-r--r-- | identity/CredentialData.h | 8 | ||||
-rw-r--r-- | identity/CredentialStore.cpp | 16 | ||||
-rw-r--r-- | identity/CredentialStore.h | 1 | ||||
-rw-r--r-- | identity/WritableCredential.cpp | 43 | ||||
-rw-r--r-- | identity/WritableCredential.h | 18 | ||||
-rw-r--r-- | identity/binder/android/security/identity/ICredential.aidl | 20 | ||||
-rw-r--r-- | identity/binder/android/security/identity/ICredentialStore.aidl | 1 |
11 files changed, 404 insertions, 70 deletions
diff --git a/identity/Android.bp b/identity/Android.bp index c0f16359..dd61930b 100644 --- a/identity/Android.bp +++ b/identity/Android.bp @@ -5,6 +5,7 @@ cc_defaults { // "-Werror", "-Wextra", "-Wunused", + "-Wno-deprecated-declarations", ], sanitize: { misc_undefined : ["integer"], @@ -38,8 +39,8 @@ cc_binary { "libkeystore-attestation-application-id", ], static_libs: [ - "android.hardware.identity-cpp", - "android.hardware.keymaster-cpp", + "android.hardware.identity-unstable-cpp", + "android.hardware.keymaster-unstable-cpp", "libcppbor", ] } diff --git a/identity/Credential.cpp b/identity/Credential.cpp index 28ba752e..4a2bae17 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 std::tuple; 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 AidlHardwareAuthToken = android::hardware::keymaster::HardwareAuthToken; 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 @@ Status Credential::loadCredential(sp<IIdentityCredentialStore> halStoreBinder) { 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 @@ Status Credential::getEntries(const vector<uint8_t>& requestMessage, 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 @@ Status Credential::getEntries(const vector<uint8_t>& requestMessage, 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 @@ Status Credential::getEntries(const vector<uint8_t>& requestMessage, 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 @@ Status Credential::getEntries(const vector<uint8_t>& requestMessage, 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 @@ Status Credential::getEntries(const vector<uint8_t>& requestMessage, 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 @@ Status Credential::getEntries(const vector<uint8_t>& requestMessage, 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 @@ Status Credential::getEntries(const vector<uint8_t>& requestMessage, } 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 @@ Status Credential::getEntries(const vector<uint8_t>& requestMessage, // 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,46 @@ Status Credential::getEntries(const vector<uint8_t>& requestMessage, 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"); + } + *_aidl_return = proofOfDeletionSignature; + 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"); } @@ -491,6 +549,20 @@ Status Credential::deleteCredential(vector<uint8_t>* _aidl_return) { 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::setReaderEphemeralPublicKey(const vector<uint8_t>& publicKey) } 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::setAvailableAuthenticationKeys(int32_t keyCount, int32_t maxU } 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 @@ Status Credential::getAuthKeysNeedingCertification(vector<AuthKeyParcel>* _aidl_ 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::getAuthKeysNeedingCertification(vector<AuthKeyParcel>* _aidl_ 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::storeStaticAuthenticationData(const AuthKeyParcel& authentica } 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 @@ Status Credential::getAuthenticationDataUsageCount(vector<int32_t>* _aidl_return 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 diff --git a/identity/Credential.h b/identity/Credential.h index e2880d98..7f085150 100644 --- a/identity/Credential.h +++ b/identity/Credential.h @@ -36,6 +36,7 @@ using ::std::string; using ::std::vector; using ::android::hardware::identity::CipherSuite; +using ::android::hardware::identity::HardwareInformation; using ::android::hardware::identity::IIdentityCredential; using ::android::hardware::identity::IIdentityCredentialStore; using ::android::hardware::identity::RequestDataItem; @@ -43,10 +44,12 @@ using ::android::hardware::identity::RequestNamespace; class Credential : public BnCredential { public: - Credential(CipherSuite cipherSuite, const string& dataPath, const string& credentialName); + Credential(CipherSuite cipherSuite, const string& dataPath, const string& credentialName, + uid_t callingUid, HardwareInformation hwInfo, + sp<IIdentityCredentialStore> halStoreBinder, int halApiVersion); ~Credential(); - Status loadCredential(sp<IIdentityCredentialStore> halStoreBinder); + Status ensureOrReplaceHalBinder(); // ICredential overrides Status createEphemeralKeyPair(vector<uint8_t>* _aidl_return) override; @@ -55,33 +58,47 @@ class Credential : public BnCredential { Status deleteCredential(vector<uint8_t>* _aidl_return) override; + Status deleteWithChallenge(const vector<uint8_t>& challenge, + vector<uint8_t>* _aidl_return) override; + + Status proveOwnership(const vector<uint8_t>& challenge, vector<uint8_t>* _aidl_return) override; + Status getCredentialKeyCertificateChain(vector<uint8_t>* _aidl_return) override; - Status selectAuthKey(bool allowUsingExhaustedKeys, int64_t* _aidl_return) override; + Status selectAuthKey(bool allowUsingExhaustedKeys, bool allowUsingExpiredKeys, + int64_t* _aidl_return) override; Status getEntries(const vector<uint8_t>& requestMessage, const vector<RequestNamespaceParcel>& requestNamespaces, const vector<uint8_t>& sessionTranscript, const vector<uint8_t>& readerSignature, bool allowUsingExhaustedKeys, - GetEntriesResultParcel* _aidl_return) override; + bool allowUsingExpiredKeys, GetEntriesResultParcel* _aidl_return) override; Status setAvailableAuthenticationKeys(int32_t keyCount, int32_t maxUsesPerKey) override; Status getAuthKeysNeedingCertification(vector<AuthKeyParcel>* _aidl_return) override; Status storeStaticAuthenticationData(const AuthKeyParcel& authenticationKey, const vector<uint8_t>& staticAuthData) override; + Status + storeStaticAuthenticationDataWithExpiration(const AuthKeyParcel& authenticationKey, + int64_t expirationDateMillisSinceEpoch, + const vector<uint8_t>& staticAuthData) override; Status getAuthenticationDataUsageCount(vector<int32_t>* _aidl_return) override; + Status update(sp<IWritableCredential>* _aidl_return) override; + private: CipherSuite cipherSuite_; string dataPath_; string credentialName_; + uid_t callingUid_; + HardwareInformation hwInfo_; + sp<IIdentityCredentialStore> halStoreBinder_; const AuthKeyData* selectedAuthKey_ = nullptr; uint64_t selectedChallenge_ = 0; - sp<CredentialData> data_; - sp<IIdentityCredential> halBinder_; + int halApiVersion_; ssize_t calcExpectedDeviceNameSpacesSize(const vector<uint8_t>& requestMessage, diff --git a/identity/CredentialData.cpp b/identity/CredentialData.cpp index b4e6641d..96c436a8 100644 --- a/identity/CredentialData.cpp +++ b/identity/CredentialData.cpp @@ -16,6 +16,8 @@ #define LOG_TAG "CredentialData" +#include <chrono> + #include <fcntl.h> #include <stdlib.h> #include <sys/stat.h> @@ -119,12 +121,15 @@ bool CredentialData::saveToDisk() const { 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)); @@ -183,9 +188,17 @@ optional<AuthKeyData> parseAuthKeyData(const cppbor::Item& item) { 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(); @@ -232,7 +245,6 @@ optional<vector<vector<uint8_t>>> parseEncryptedChunks(const cppbor::Item& item) } bool CredentialData::loadFromDisk() { - // Reset all data. credentialData_.clear(); attestationCertificate_.clear(); @@ -487,16 +499,28 @@ const vector<AuthKeyData>& CredentialData::getAuthKeyDatas() const { return authKeyDatas_; } -const AuthKeyData* CredentialData::selectAuthKey(bool allowUsingExhaustedKeys) { +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; - int candidateNum = -1; 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; - candidateNum = n; } } n++; @@ -510,6 +534,28 @@ const AuthKeyData* CredentialData::selectAuthKey(bool 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; } @@ -519,8 +565,14 @@ CredentialData::getAuthKeysNeedingCertification(const sp<IIdentityCredential>& h 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 newKeyNeeded = (data.certificate.size() == 0) || (data.useCount >= maxUsesPerKey_); + 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; @@ -543,11 +595,13 @@ CredentialData::getAuthKeysNeedingCertification(const sp<IIdentityCredential>& h } 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(); diff --git a/identity/CredentialData.h b/identity/CredentialData.h index 79958281..b037997b 100644 --- a/identity/CredentialData.h +++ b/identity/CredentialData.h @@ -55,6 +55,7 @@ struct AuthKeyData { vector<uint8_t> certificate; vector<uint8_t> keyBlob; + int64_t expirationDateMillisSinceEpoch; vector<uint8_t> staticAuthenticationData; vector<uint8_t> pendingCertificate; vector<uint8_t> pendingKeyBlob; @@ -106,17 +107,22 @@ class CredentialData : public RefBase { const vector<AuthKeyData>& getAuthKeyDatas() const; + pair<int /* keyCount */, int /*maxUsersPerKey */> getAvailableAuthenticationKeys(); + // Returns |nullptr| if a suitable key cannot be found. Otherwise returns // the authentication and increases its use-count. - const AuthKeyData* selectAuthKey(bool allowUsingExhaustedKeys); + const AuthKeyData* selectAuthKey(bool allowUsingExhaustedKeys, bool allowUsingExpiredKeys); optional<vector<vector<uint8_t>>> getAuthKeysNeedingCertification(const sp<IIdentityCredential>& halBinder); bool storeStaticAuthenticationData(const vector<uint8_t>& authenticationKey, + int64_t expirationDateMillisSinceEpoch, const vector<uint8_t>& staticAuthData); private: + AuthKeyData* findAuthKey_(bool allowUsingExhaustedKeys, bool allowUsingExpiredKeys); + // Set by constructor. // string dataPath_; diff --git a/identity/CredentialStore.cpp b/identity/CredentialStore.cpp index e3a825ba..f77294ee 100644 --- a/identity/CredentialStore.cpp +++ b/identity/CredentialStore.cpp @@ -41,11 +41,12 @@ bool CredentialStore::init() { LOG(ERROR) << "Error getting hardware information: " << status.toString8(); return false; } + halApiVersion_ = hal_->getInterfaceVersion(); - LOG(INFO) << "Connected to Identity Credential HAL with name '" << hwInfo_.credentialStoreName - << "' authored by '" << hwInfo_.credentialStoreAuthorName << "' with chunk size " - << hwInfo_.dataChunkSize << " and directoAccess set to " - << (hwInfo_.isDirectAccess ? "true" : "false"); + LOG(INFO) << "Connected to Identity Credential HAL with API version " << halApiVersion_ + << " and name '" << hwInfo_.credentialStoreName << "' authored by '" + << hwInfo_.credentialStoreAuthorName << "' with chunk size " << hwInfo_.dataChunkSize + << " and directoAccess set to " << (hwInfo_.isDirectAccess ? "true" : "false"); return true; } @@ -89,7 +90,7 @@ Status CredentialStore::createCredential(const std::string& credentialName, } sp<IWritableCredential> writableCredential = new WritableCredential( - dataPath_, credentialName, docType, hwInfo_.dataChunkSize, halWritableCredential); + dataPath_, credentialName, docType, false, hwInfo_, halWritableCredential, halApiVersion_); *_aidl_return = writableCredential; return Status::ok(); } @@ -112,9 +113,10 @@ Status CredentialStore::getCredentialByName(const std::string& credentialName, i // Note: IdentityCredentialStore.java's CipherSuite enumeration and CipherSuite from the // HAL is manually kept in sync. So this cast is safe. - sp<Credential> credential = new Credential(CipherSuite(cipherSuite), dataPath_, credentialName); + sp<Credential> credential = new Credential(CipherSuite(cipherSuite), dataPath_, credentialName, + callingUid, hwInfo_, hal_, halApiVersion_); - Status loadStatus = credential->loadCredential(hal_); + Status loadStatus = credential->ensureOrReplaceHalBinder(); if (!loadStatus.isOk()) { LOG(ERROR) << "Error loading credential"; } else { diff --git a/identity/CredentialStore.h b/identity/CredentialStore.h index 24b2b4db..15da4eba 100644 --- a/identity/CredentialStore.h +++ b/identity/CredentialStore.h @@ -57,6 +57,7 @@ class CredentialStore : public BnCredentialStore { string dataPath_; sp<IIdentityCredentialStore> hal_; + int halApiVersion_; HardwareInformation hwInfo_; }; diff --git a/identity/WritableCredential.cpp b/identity/WritableCredential.cpp index a932dcf5..d0688b8d 100644 --- a/identity/WritableCredential.cpp +++ b/identity/WritableCredential.cpp @@ -39,13 +39,19 @@ using ::android::hardware::identity::SecureAccessControlProfile; using ::android::hardware::identity::support::chunkVector; WritableCredential::WritableCredential(const string& dataPath, const string& credentialName, - const string& docType, size_t dataChunkSize, - sp<IWritableIdentityCredential> halBinder) - : dataPath_(dataPath), credentialName_(credentialName), docType_(docType), - dataChunkSize_(dataChunkSize), halBinder_(halBinder) {} + const string& docType, bool isUpdate, + HardwareInformation hwInfo, + sp<IWritableIdentityCredential> halBinder, int halApiVersion) + : dataPath_(dataPath), credentialName_(credentialName), docType_(docType), isUpdate_(isUpdate), + hwInfo_(std::move(hwInfo)), halBinder_(halBinder), halApiVersion_(halApiVersion) {} WritableCredential::~WritableCredential() {} +void WritableCredential::setCredentialUpdatedCallback( + std::function<void()>&& onCredentialUpdatedCallback) { + onCredentialUpdatedCallback_ = onCredentialUpdatedCallback; +} + Status WritableCredential::ensureAttestationCertificateExists(const vector<uint8_t>& challenge) { if (!attestationCertificate_.empty()) { return Status::ok(); @@ -79,7 +85,10 @@ Status WritableCredential::ensureAttestationCertificateExists(const vector<uint8 Status WritableCredential::getCredentialKeyCertificateChain(const vector<uint8_t>& challenge, vector<uint8_t>* _aidl_return) { - + if (isUpdate_) { + return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC, + "Cannot be called for an update"); + } Status ensureStatus = ensureAttestationCertificateExists(challenge); if (!ensureStatus.isOk()) { return ensureStatus; @@ -89,6 +98,15 @@ Status WritableCredential::getCredentialKeyCertificateChain(const vector<uint8_t return Status::ok(); } +void WritableCredential::setAttestationCertificate(const vector<uint8_t>& attestationCertificate) { + attestationCertificate_ = attestationCertificate; +} + +void WritableCredential::setAvailableAuthenticationKeys(int keyCount, int maxUsesPerKey) { + keyCount_ = keyCount; + maxUsesPerKey_ = maxUsesPerKey; +} + ssize_t WritableCredential::calcExpectedProofOfProvisioningSize( const vector<AccessControlProfileParcel>& accessControlProfiles, const vector<EntryNamespaceParcel>& entryNamespaces) { @@ -149,9 +167,12 @@ Status WritableCredential::personalize(const vector<AccessControlProfileParcel>& accessControlProfiles, const vector<EntryNamespaceParcel>& entryNamespaces, int64_t secureUserId, vector<uint8_t>* _aidl_return) { - Status ensureStatus = ensureAttestationCertificateExists({0x00}); // Challenge cannot be empty. - if (!ensureStatus.isOk()) { - return ensureStatus; + if (!isUpdate_) { + Status ensureStatus = + ensureAttestationCertificateExists({0x00}); // Challenge cannot be empty. + if (!ensureStatus.isOk()) { + return ensureStatus; + } } uid_t callingUid = android::IPCThreadState::self()->getCallingUid(); @@ -203,7 +224,7 @@ WritableCredential::personalize(const vector<AccessControlProfileParcel>& access for (const EntryNamespaceParcel& ensParcel : entryNamespaces) { for (const EntryParcel& eParcel : ensParcel.entries) { - vector<vector<uint8_t>> chunks = chunkVector(eParcel.value, dataChunkSize_); + vector<vector<uint8_t>> chunks = chunkVector(eParcel.value, hwInfo_.dataChunkSize); vector<int32_t> ids; std::copy(eParcel.accessControlProfileIds.begin(), @@ -240,11 +261,15 @@ WritableCredential::personalize(const vector<AccessControlProfileParcel>& access } data.setCredentialData(credentialData); + data.setAvailableAuthenticationKeys(keyCount_, maxUsesPerKey_); + if (!data.saveToDisk()) { return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC, "Error saving credential data to disk"); } + onCredentialUpdatedCallback_(); + *_aidl_return = proofOfProvisioningSignature; return Status::ok(); } diff --git a/identity/WritableCredential.h b/identity/WritableCredential.h index eb63aca2..6ff31aec 100644 --- a/identity/WritableCredential.h +++ b/identity/WritableCredential.h @@ -29,6 +29,7 @@ namespace security { namespace identity { using ::android::binder::Status; +using ::android::hardware::identity::HardwareInformation; using ::android::hardware::identity::IWritableIdentityCredential; using ::std::string; using ::std::vector; @@ -36,9 +37,15 @@ using ::std::vector; class WritableCredential : public BnWritableCredential { public: WritableCredential(const string& dataPath, const string& credentialName, const string& docType, - size_t dataChunkSize, sp<IWritableIdentityCredential> halBinder); + bool isUpdate, HardwareInformation hwInfo, + sp<IWritableIdentityCredential> halBinder, int halApiVersion); ~WritableCredential(); + // Used when updating a credential + void setAttestationCertificate(const vector<uint8_t>& attestationCertificate); + void setAvailableAuthenticationKeys(int keyCount, int maxUsesPerKey); + void setCredentialUpdatedCallback(std::function<void()>&& onCredentialUpdatedCallback); + // IWritableCredential overrides Status getCredentialKeyCertificateChain(const vector<uint8_t>& challenge, vector<uint8_t>* _aidl_return) override; @@ -51,9 +58,16 @@ class WritableCredential : public BnWritableCredential { string dataPath_; string credentialName_; string docType_; - size_t dataChunkSize_; + bool isUpdate_; + HardwareInformation hwInfo_; sp<IWritableIdentityCredential> halBinder_; + int halApiVersion_; + vector<uint8_t> attestationCertificate_; + int keyCount_ = 0; + int maxUsesPerKey_ = 1; + + std::function<void()> onCredentialUpdatedCallback_ = []() {}; ssize_t calcExpectedProofOfProvisioningSize( const vector<AccessControlProfileParcel>& accessControlProfiles, diff --git a/identity/binder/android/security/identity/ICredential.aidl b/identity/binder/android/security/identity/ICredential.aidl index 7bd0df79..2165810f 100644 --- a/identity/binder/android/security/identity/ICredential.aidl +++ b/identity/binder/android/security/identity/ICredential.aidl @@ -16,6 +16,8 @@ package android.security.identity; +import android.security.identity.IWritableCredential; + import android.security.identity.RequestNamespaceParcel; import android.security.identity.GetEntriesResultParcel; import android.security.identity.AuthKeyParcel; @@ -40,23 +42,35 @@ interface ICredential { void setReaderEphemeralPublicKey(in byte[] publicKey); byte[] deleteCredential(); + byte[] deleteWithChallenge(in byte[] challenge); + + byte[] proveOwnership(in byte[] challenge); byte[] getCredentialKeyCertificateChain(); - long selectAuthKey(in boolean allowUsingExhaustedKeys); + long selectAuthKey(in boolean allowUsingExhaustedKeys, + in boolean allowUsingExpiredKeys); GetEntriesResultParcel getEntries(in byte[] requestMessage, in RequestNamespaceParcel[] requestNamespaces, in byte[] sessionTranscript, in byte[] readerSignature, - in boolean allowUsingExhaustedKeys); + in boolean allowUsingExhaustedKeys, + in boolean allowUsingExpiredKeys); void setAvailableAuthenticationKeys(in int keyCount, in int maxUsesPerKey); AuthKeyParcel[] getAuthKeysNeedingCertification(); - void storeStaticAuthenticationData(in AuthKeyParcel authenticationKey, in byte[] staticAuthData); + void storeStaticAuthenticationData(in AuthKeyParcel authenticationKey, + in byte[] staticAuthData); + + void storeStaticAuthenticationDataWithExpiration(in AuthKeyParcel authenticationKey, + in long expirationDateMillisSinceEpoch, + in byte[] staticAuthData); int[] getAuthenticationDataUsageCount(); + + IWritableCredential update(); } diff --git a/identity/binder/android/security/identity/ICredentialStore.aidl b/identity/binder/android/security/identity/ICredentialStore.aidl index 10398312..8357f47b 100644 --- a/identity/binder/android/security/identity/ICredentialStore.aidl +++ b/identity/binder/android/security/identity/ICredentialStore.aidl @@ -39,6 +39,7 @@ interface ICredentialStore { const int ERROR_AUTHENTICATION_KEY_NOT_FOUND = 9; const int ERROR_INVALID_ITEMS_REQUEST_MESSAGE = 10; const int ERROR_SESSION_TRANSCRIPT_MISMATCH = 11; + const int ERROR_NOT_SUPPORTED = 12; SecurityHardwareInfoParcel getSecurityHardwareInfo(); |