/* * 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 #include #include #include #include "Credential.h" #include "CredentialData.h" #include "Util.h" #include "WritableCredential.h" namespace android { namespace security { namespace identity { using std::optional; using std::promise; using std::tuple; using ::android::hardware::identity::IWritableIdentityCredential; using ::android::hardware::identity::support::ecKeyPairGetPkcs12; using ::android::hardware::identity::support::ecKeyPairGetPrivateKey; using ::android::hardware::identity::support::ecKeyPairGetPublicKey; using ::android::hardware::identity::support::sha256; using android::hardware::keymaster::SecurityLevel; using android::hardware::keymaster::V4_0::HardwareAuthToken; using android::hardware::keymaster::V4_0::VerificationToken; using AidlHardwareAuthToken = android::hardware::keymaster::HardwareAuthToken; using AidlVerificationToken = android::hardware::keymaster::VerificationToken; using KeyMintAuthToken = ::aidl::android::hardware::security::keymint::HardwareAuthToken; using ::aidl::android::hardware::security::secureclock::TimeStampToken; using ::aidl::android::security::authorization::AuthorizationTokens; using ::aidl::android::security::authorization::IKeystoreAuthorization; Credential::Credential(CipherSuite cipherSuite, const std::string& dataPath, const std::string& credentialName, uid_t callingUid, HardwareInformation hwInfo, sp halStoreBinder, sp halSessionBinder, int halApiVersion) : cipherSuite_(cipherSuite), dataPath_(dataPath), credentialName_(credentialName), callingUid_(callingUid), hwInfo_(std::move(hwInfo)), halStoreBinder_(halStoreBinder), halSessionBinder_(halSessionBinder), halApiVersion_(halApiVersion) {} Credential::~Credential() {} Status Credential::ensureOrReplaceHalBinder() { sp 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 we're in a session we explicitly don't get the binder to IIdentityCredential until // it's used in getEntries() which is the only method call allowed for sessions. // // Why? This is because we want to throw the IIdentityCredential object away as soon as it's // used because the HAL only guarantees a single IIdentityCredential object alive at a time // and in a session there may be multiple credentials in play and we want to do multiple // getEntries() calls on all of them. // if (!halSessionBinder_) { sp halBinder; Status status = 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) { return halStatusToError(status, ICredentialStore::ERROR_CIPHER_SUITE_NOT_SUPPORTED); } } if (!status.isOk()) { LOG(ERROR) << "Error getting HAL binder"; return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC); } halBinder_ = halBinder; } return Status::ok(); } Status Credential::getCredentialKeyCertificateChain(std::vector* _aidl_return) { if (halSessionBinder_) { return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC, "Cannot be used with session"); } sp 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, bool allowUsingExpiredKeys, bool incrementUsageCount, int64_t* _aidl_return) { if (halSessionBinder_) { return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC, "Cannot be used with session"); } sp 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"); } // We just check if a key is available, we actually don't store it since we // don't keep CredentialData around between binder calls. const AuthKeyData* authKey = data->selectAuthKey(allowUsingExhaustedKeys, allowUsingExpiredKeys, incrementUsageCount); if (authKey == nullptr) { return Status::fromServiceSpecificError( ICredentialStore::ERROR_NO_AUTHENTICATION_KEY_AVAILABLE, "No suitable authentication key available"); } if (!ensureChallenge()) { return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC, "Error getting challenge (bug in HAL or TA)"); } *_aidl_return = selectedChallenge_; return Status::ok(); } bool Credential::ensureChallenge() { if (selectedChallenge_ != 0) { return true; } int64_t challenge; // If we're in a session, the challenge is selected by the session if (halSessionBinder_) { Status status = halSessionBinder_->getAuthChallenge(&challenge); if (!status.isOk()) { LOG(ERROR) << "Error getting challenge from session: " << status.exceptionMessage(); return false; } } else { Status status = halBinder_->createAuthChallenge(&challenge); if (!status.isOk()) { LOG(ERROR) << "Error getting challenge: " << status.exceptionMessage(); return false; } } if (challenge == 0) { LOG(ERROR) << "Returned challenge is 0 (bug in HAL or TA)"; return false; } selectedChallenge_ = challenge; return true; } // Returns false if an error occurred communicating with keystore. // bool getTokensFromKeystore2(uint64_t challenge, uint64_t secureUserId, unsigned int authTokenMaxAgeMillis, AidlHardwareAuthToken& aidlAuthToken, AidlVerificationToken& aidlVerificationToken) { // try to connect to IKeystoreAuthorization AIDL service first. AIBinder* authzAIBinder = AServiceManager_checkService("android.security.authorization"); ::ndk::SpAIBinder authzBinder(authzAIBinder); auto authzService = IKeystoreAuthorization::fromBinder(authzBinder); if (authzService) { AuthorizationTokens authzTokens; auto result = authzService->getAuthTokensForCredStore(challenge, secureUserId, authTokenMaxAgeMillis, &authzTokens); // Convert KeyMint auth token to KeyMaster authtoken, only if tokens are // returned if (result.isOk()) { KeyMintAuthToken keymintAuthToken = authzTokens.authToken; aidlAuthToken.challenge = keymintAuthToken.challenge; aidlAuthToken.userId = keymintAuthToken.userId; aidlAuthToken.authenticatorId = keymintAuthToken.authenticatorId; aidlAuthToken.authenticatorType = ::android::hardware::keymaster::HardwareAuthenticatorType( int32_t(keymintAuthToken.authenticatorType)); aidlAuthToken.timestamp.milliSeconds = keymintAuthToken.timestamp.milliSeconds; aidlAuthToken.mac = keymintAuthToken.mac; // Convert timestamp token to KeyMaster verification token TimeStampToken timestampToken = authzTokens.timestampToken; aidlVerificationToken.challenge = timestampToken.challenge; aidlVerificationToken.timestamp.milliSeconds = timestampToken.timestamp.milliSeconds; // Legacy verification tokens were always minted by TEE. aidlVerificationToken.securityLevel = SecurityLevel::TRUSTED_ENVIRONMENT; aidlVerificationToken.mac = timestampToken.mac; } else { if (result.getServiceSpecificError() == 0) { // Here we differentiate the errors occurred during communication // from the service specific errors. LOG(ERROR) << "Error getting tokens from keystore2: " << result.getDescription(); return false; } else { // Log the reason for not receiving auth tokens from keystore2. LOG(INFO) << "Auth tokens were not received due to: " << result.getDescription(); } } return true; } else { LOG(ERROR) << "Error connecting to IKeystoreAuthorization service"; return false; } } Status Credential::getEntries(const vector& requestMessage, const vector& requestNamespaces, const vector& sessionTranscript, const vector& readerSignature, bool allowUsingExhaustedKeys, bool allowUsingExpiredKeys, bool incrementUsageCount, GetEntriesResultParcel* _aidl_return) { GetEntriesResultParcel ret; sp 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 used in a session, get the binder on demand... // sp halBinder = halBinder_; if (halSessionBinder_) { if (halBinder) { LOG(ERROR) << "Unexpected HAL binder for session"; return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC, "Unexpected HAL binder for session"); } Status status = halSessionBinder_->getCredential(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) { return halStatusToError(status, ICredentialStore::ERROR_CIPHER_SUITE_NOT_SUPPORTED); } } if (!status.isOk()) { LOG(ERROR) << "Error getting HAL binder"; return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC); } } // 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 requestCounts; const vector& 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 // Credential HAL implementation where the TA uses a 32-bit word to indicate // which profiles are authorized. for (const SecureAccessControlProfile& profile : allProfiles) { if (profile.id < 0 || profile.id >= 32) { return Status::fromServiceSpecificError( ICredentialStore::ERROR_GENERIC, "Invalid accessProfileId in profile (must be between 0 and 31)"); } } vector includeProfile(32); for (const RequestNamespaceParcel& rns : requestNamespaces) { size_t numEntriesInNsToRequest = 0; for (const RequestEntryParcel& rep : rns.entries) { if (data->hasEntryData(rns.namespaceName, rep.name)) { numEntriesInNsToRequest++; } optional 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; return Status::fromServiceSpecificError( ICredentialStore::ERROR_GENERIC, "Invalid accessProfileId in entry (must be between 0 and 31)"); } includeProfile[id] = true; } } } requestCounts.push_back(numEntriesInNsToRequest); } // Now that we know which profiles are needed, send only those to the // HAL. vector selectedProfiles; for (size_t n = 0; n < allProfiles.size(); n++) { if (includeProfile[allProfiles[n].id]) { selectedProfiles.push_back(allProfiles[n]); } } // Calculate the highest [1] non-zero timeout and if user-auth is needed // ... we need this to select an appropriate authToken. // // [1] : Why do we request the highest timeout and not the lowest? Well, we // return partial results in getEntries e.g. if some data elements // fail to authorize we'll still return the ones that did not fail. So // e.g. consider data elements A and B where A has an ACP with 60 // seconds and B has an ACP with 3600 seconds. In this case we'll be // fine with getting an authToken for e.g. 2400 seconds which would // mean returning only B. // bool userAuthNeeded = false; unsigned int authTokenMaxAgeMillis = 0; for (auto& profile : selectedProfiles) { if (profile.userAuthenticationRequired) { userAuthNeeded = true; if (profile.timeoutMillis > 0) { if (profile.timeoutMillis > authTokenMaxAgeMillis) { authTokenMaxAgeMillis = profile.timeoutMillis; } } } } // Reset tokens and only get them if they're actually needed, e.g. if user authentication // is needed in any of the access control profiles for data items being requested. // AidlHardwareAuthToken aidlAuthToken; AidlVerificationToken aidlVerificationToken; aidlAuthToken.challenge = 0; aidlAuthToken.userId = 0; aidlAuthToken.authenticatorId = 0; aidlAuthToken.authenticatorType = ::android::hardware::keymaster::HardwareAuthenticatorType::NONE; aidlAuthToken.timestamp.milliSeconds = 0; aidlAuthToken.mac.clear(); aidlVerificationToken.challenge = 0; aidlVerificationToken.timestamp.milliSeconds = 0; aidlVerificationToken.securityLevel = ::android::hardware::keymaster::SecurityLevel::SOFTWARE; aidlVerificationToken.mac.clear(); if (userAuthNeeded) { // If user authentication is needed, always get a challenge from the // HAL/TA since it'll need it to check the returned VerificationToken // for freshness. if (!ensureChallenge()) { return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC, "Error getting challenge (bug in HAL or TA)"); } // Note: if all selected profiles require auth-on-every-presentation // then authTokenMaxAgeMillis will be 0 (because timeoutMillis for each // profile is 0). Which means that keystore will only return an // AuthToken if its challenge matches what we pass, regardless of its // age. This is intended b/c the HAL/TA will check not care about // the age in this case, it only cares that the challenge matches. // // Otherwise, if one or more of the profiles is auth-with-a-timeout then // authTokenMaxAgeMillis will be set to the largest of those // timeouts. We'll get an AuthToken which satisfies this deadline if it // exists. This authToken _may_ have the requested challenge but it's // not a guarantee and it's also not required. // if (!getTokensFromKeystore2(selectedChallenge_, data->getSecureUserId(), authTokenMaxAgeMillis, aidlAuthToken, aidlVerificationToken)) { LOG(ERROR) << "Error getting tokens from keystore2"; return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC, "Error getting tokens from keystore2"); } } // Reuse the same AuthKey over multiple getEntries() calls. // bool updateUseCountOnDisk = false; if (!selectedAuthKey_) { // Note that the selectAuthKey() method is only called if a CryptoObject is involved at // the Java layer. So we could end up with no previously selected auth key and we may // need one. // const AuthKeyData* authKey = data->selectAuthKey( allowUsingExhaustedKeys, allowUsingExpiredKeys, incrementUsageCount); if (authKey == nullptr) { // If no authKey is available, consider it an error only when a // SessionTranscript was provided. // // We allow no SessionTranscript to be provided because it makes // the API simpler to deal with insofar it can be used without having // to generate any authentication keys. // // In this "no SessionTranscript is provided" mode we don't return // DeviceNameSpaces nor a MAC over DeviceAuthentication so we don't // need a device key. // if (sessionTranscript.size() > 0) { return Status::fromServiceSpecificError( ICredentialStore::ERROR_NO_AUTHENTICATION_KEY_AVAILABLE, "No suitable authentication key available and one is needed"); } } else { // We did find an authKey. Store its contents for future getEntries() calls. updateUseCountOnDisk = true; selectedAuthKeySigningKeyBlob_ = authKey->keyBlob; selectedAuthKeyStaticAuthData_ = authKey->staticAuthenticationData; } selectedAuthKey_ = true; } // Pass the HAL enough information to allow calculating the size of // DeviceNameSpaces ahead of time. vector halRequestNamespaces; for (const RequestNamespaceParcel& rns : requestNamespaces) { RequestNamespace ns; ns.namespaceName = rns.namespaceName; for (const RequestEntryParcel& rep : rns.entries) { optional entryData = data->getEntryData(rns.namespaceName, rep.name); if (entryData) { RequestDataItem di; di.name = rep.name; di.size = entryData.value().size; di.accessControlProfileIds = entryData.value().accessControlProfileIds; ns.items.push_back(di); } } if (ns.items.size() > 0) { halRequestNamespaces.push_back(ns); } } // This is not catastrophic, we might be dealing with a version 1 implementation which // doesn't have this method. Status status = halBinder->setRequestedNamespaces(halRequestNamespaces); if (!status.isOk()) { LOG(INFO) << "Failed setting expected requested namespaces, assuming V1 HAL " << "and continuing"; } // Pass the verification token. Failure is OK, this method isn't in the V1 HAL. status = halBinder->setVerificationToken(aidlVerificationToken); if (!status.isOk()) { LOG(INFO) << "Failed setting verification token, assuming V1 HAL " << "and continuing"; } status = halBinder->startRetrieval(selectedProfiles, aidlAuthToken, requestMessage, selectedAuthKeySigningKeyBlob_, sessionTranscript, readerSignature, requestCounts); if (!status.isOk() && status.exceptionCode() == binder::Status::EX_SERVICE_SPECIFIC) { int code = status.serviceSpecificErrorCode(); if (code == IIdentityCredentialStore::STATUS_EPHEMERAL_PUBLIC_KEY_NOT_FOUND) { return halStatusToError(status, ICredentialStore::ERROR_EPHEMERAL_PUBLIC_KEY_NOT_FOUND); } else if (code == IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED) { return halStatusToError(status, ICredentialStore::ERROR_INVALID_READER_SIGNATURE); } else if (code == IIdentityCredentialStore::STATUS_INVALID_ITEMS_REQUEST_MESSAGE) { return halStatusToError(status, ICredentialStore::ERROR_INVALID_ITEMS_REQUEST_MESSAGE); } else if (code == IIdentityCredentialStore::STATUS_SESSION_TRANSCRIPT_MISMATCH) { return halStatusToError(status, ICredentialStore::ERROR_SESSION_TRANSCRIPT_MISMATCH); } } if (!status.isOk()) { return halStatusToGenericError(status); } for (const RequestNamespaceParcel& rns : requestNamespaces) { ResultNamespaceParcel resultNamespaceParcel; resultNamespaceParcel.namespaceName = rns.namespaceName; for (const RequestEntryParcel& rep : rns.entries) { ResultEntryParcel resultEntryParcel; resultEntryParcel.name = rep.name; optional 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, 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) { resultEntryParcel.status = STATUS_USER_AUTHENTICATION_FAILED; resultNamespaceParcel.entries.push_back(resultEntryParcel); continue; } else if (code == IIdentityCredentialStore::STATUS_READER_AUTHENTICATION_FAILED) { resultEntryParcel.status = STATUS_READER_AUTHENTICATION_FAILED; resultNamespaceParcel.entries.push_back(resultEntryParcel); continue; } else if (code == IIdentityCredentialStore::STATUS_NOT_IN_REQUEST_MESSAGE) { resultEntryParcel.status = STATUS_NOT_IN_REQUEST_MESSAGE; resultNamespaceParcel.entries.push_back(resultEntryParcel); continue; } else if (code == IIdentityCredentialStore::STATUS_NO_ACCESS_CONTROL_PROFILES) { resultEntryParcel.status = STATUS_NO_ACCESS_CONTROL_PROFILES; resultNamespaceParcel.entries.push_back(resultEntryParcel); continue; } } if (!status.isOk()) { return halStatusToGenericError(status); } vector value; for (const auto& encryptedChunk : eData.value().encryptedChunks) { vector chunk; status = halBinder->retrieveEntryValue(encryptedChunk, &chunk); if (!status.isOk()) { return halStatusToGenericError(status); } value.insert(value.end(), chunk.begin(), chunk.end()); } resultEntryParcel.status = STATUS_OK; resultEntryParcel.value = value; resultNamespaceParcel.entries.push_back(resultEntryParcel); } ret.resultNamespaces.push_back(resultNamespaceParcel); } // API version 5 (feature version 202301) supports both MAC and ECDSA signature. if (halApiVersion_ >= 5) { status = halBinder->finishRetrievalWithSignature(&ret.mac, &ret.deviceNameSpaces, &ret.signature); if (!status.isOk()) { return halStatusToGenericError(status); } } else { status = halBinder->finishRetrieval(&ret.mac, &ret.deviceNameSpaces); if (!status.isOk()) { return halStatusToGenericError(status); } } ret.staticAuthenticationData = selectedAuthKeyStaticAuthData_; // Ensure useCount is updated on disk. if (updateUseCountOnDisk) { if (!data->saveToDisk()) { return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC, "Error saving data"); } } *_aidl_return = ret; return Status::ok(); } Status Credential::deleteCredential(vector* _aidl_return) { if (halSessionBinder_) { return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC, "Cannot be used with session"); } vector proofOfDeletionSignature; sp 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()) { return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC, "Error deleting credential data on disk"); } *_aidl_return = proofOfDeletionSignature; return Status::ok(); } Status Credential::deleteWithChallenge(const vector& challenge, vector* _aidl_return) { if (halApiVersion_ < 3) { return Status::fromServiceSpecificError(ICredentialStore::ERROR_NOT_SUPPORTED, "Not implemented by HAL"); } if (halSessionBinder_) { return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC, "Cannot be used with session"); } vector proofOfDeletionSignature; sp 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& challenge, vector* _aidl_return) { if (halApiVersion_ < 3) { return Status::fromServiceSpecificError(ICredentialStore::ERROR_NOT_SUPPORTED, "Not implemented by HAL"); } if (halSessionBinder_) { return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC, "Cannot be used with session"); } vector proofOfOwnershipSignature; Status status = halBinder_->proveOwnership(challenge, &proofOfOwnershipSignature); if (!status.isOk()) { return halStatusToGenericError(status); } *_aidl_return = proofOfOwnershipSignature; return Status::ok(); } Status Credential::createEphemeralKeyPair(vector* _aidl_return) { if (halSessionBinder_) { return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC, "Cannot be used with session"); } vector keyPair; Status status = halBinder_->createEphemeralKeyPair(&keyPair); if (!status.isOk()) { return halStatusToGenericError(status); } time_t nowSeconds = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); time_t validityNotBefore = nowSeconds; time_t validityNotAfter = nowSeconds + 24 * 60 * 60; optional> pkcs12Bytes = ecKeyPairGetPkcs12(keyPair, "ephemeralKey", // Alias for key "0", // Serial, as a decimal number "Credstore", // Issuer "Ephemeral Key", // Subject validityNotBefore, validityNotAfter); if (!pkcs12Bytes) { return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC, "Error creating PKCS#12 structure for key pair"); } *_aidl_return = pkcs12Bytes.value(); return Status::ok(); } Status Credential::setReaderEphemeralPublicKey(const vector& publicKey) { if (halSessionBinder_) { return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC, "Cannot be used with session"); } Status status = halBinder_->setReaderEphemeralPublicKey(publicKey); if (!status.isOk()) { return halStatusToGenericError(status); } return Status::ok(); } Status Credential::setAvailableAuthenticationKeys(int32_t keyCount, int32_t maxUsesPerKey, int64_t minValidTimeMillis) { if (halSessionBinder_) { return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC, "Cannot be used with session"); } sp 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, minValidTimeMillis); if (!data->saveToDisk()) { return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC, "Error saving data"); } return Status::ok(); } Status Credential::getAuthKeysNeedingCertification(vector* _aidl_return) { if (halSessionBinder_) { return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC, "Cannot be used with session"); } sp 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>> keysNeedingCert = data->getAuthKeysNeedingCertification(halBinder_); if (!keysNeedingCert) { return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC, "Error getting auth keys neededing certification"); } vector authKeyParcels; for (const vector& key : keysNeedingCert.value()) { AuthKeyParcel authKeyParcel; authKeyParcel.x509cert = key; authKeyParcels.push_back(authKeyParcel); } if (!data->saveToDisk()) { return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC, "Error saving data"); } *_aidl_return = authKeyParcels; return Status::ok(); } Status Credential::storeStaticAuthenticationData(const AuthKeyParcel& authenticationKey, const vector& staticAuthData) { if (halSessionBinder_) { return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC, "Cannot be used with session"); } sp 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::max(), 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"); } return Status::ok(); } Status Credential::storeStaticAuthenticationDataWithExpiration(const AuthKeyParcel& authenticationKey, int64_t expirationDateMillisSinceEpoch, const vector& staticAuthData) { if (halSessionBinder_) { return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC, "Cannot be used with session"); } sp 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"); } return Status::ok(); } Status Credential::getAuthenticationDataUsageCount(vector* _aidl_return) { if (halSessionBinder_) { return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC, "Cannot be used with session"); } sp 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& authKeyDatas = data->getAuthKeyDatas(); vector ret; for (const AuthKeyData& authKeyData : authKeyDatas) { ret.push_back(authKeyData.useCount); } *_aidl_return = ret; return Status::ok(); } Status Credential::getAuthenticationDataExpirations(vector* _aidl_return) { if (halSessionBinder_) { return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC, "Cannot be used with session"); } sp 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& authKeyDatas = data->getAuthKeyDatas(); vector ret; ret.reserve(authKeyDatas.size()); for (const AuthKeyData& authKeyData : authKeyDatas) { // Note: value is INT64_MAX if expiration date is not set. ret.push_back(authKeyData.expirationDateMillisSinceEpoch); } *_aidl_return = ret; return Status::ok(); } optional extractDocType(const vector& 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* _aidl_return) { if (halApiVersion_ < 3) { return Status::fromServiceSpecificError(ICredentialStore::ERROR_NOT_SUPPORTED, "Not implemented by HAL"); } if (halSessionBinder_) { return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC, "Cannot be used with session"); } sp 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 halWritableCredential; Status status = halBinder_->updateCredential(&halWritableCredential); if (!status.isOk()) { return halStatusToGenericError(status); } optional 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 = new WritableCredential( dataPath_, credentialName_, docType.value(), true, hwInfo_, halWritableCredential); writableCredential->setAttestationCertificate(data->getAttestationCertificate()); auto [keyCount, maxUsesPerKey, minValidTimeMillis] = data->getAvailableAuthenticationKeys(); writableCredential->setAvailableAuthenticationKeys(keyCount, maxUsesPerKey, minValidTimeMillis); // Because its data has changed, we need to replace the binder for the // IIdentityCredential when the credential has been updated... otherwise the // remote object will have stale data for future calls, for example // getAuthKeysNeedingCertification(). // // The way this is implemented is that setCredentialToReloadWhenUpdated() // instructs the WritableCredential to call writableCredentialPersonalized() // on |this|. // // writableCredential->setCredentialToReloadWhenUpdated(this); *_aidl_return = writableCredential; return Status::ok(); } void Credential::writableCredentialPersonalized() { Status status = ensureOrReplaceHalBinder(); if (!status.isOk()) { LOG(ERROR) << "Error reloading credential"; } } } // namespace identity } // namespace security } // namespace android