c239db4114
This change adds support for specifying that an AuthKey should be replaced if it's going to expire within a certain amount of time configurable by the application. This also adds a way for the application to learn about the expiration time of currently configured AuthKeys. Combined these two changes allow an application to get a perfect picture of which AuthKeys are available, when they expire, and allows the application to refresh AuthKeys well ahead of expiration dates. Also remove checking storeStaticAuthenticationDataWithExpiration() is only available on HAL version 3 and later (feature version 202101 and later). This works on any HAL version. Bug: 241912421 Test: atest VtsHalIdentityTargetTest Test: atest android.security.identity.cts Change-Id: Ic8274088035c31f73ad61645ee5e0281b3460837
946 lines
42 KiB
C++
946 lines
42 KiB
C++
/*
|
|
* Copyright (c) 2019, The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#define LOG_TAG "credstore"
|
|
|
|
#include <android-base/logging.h>
|
|
#include <android/binder_manager.h>
|
|
#include <android/hardware/identity/support/IdentityCredentialSupport.h>
|
|
|
|
#include <android/security/identity/ICredentialStore.h>
|
|
|
|
#include <binder/IPCThreadState.h>
|
|
#include <binder/IServiceManager.h>
|
|
#include <keymasterV4_0/keymaster_utils.h>
|
|
|
|
#include <cppbor.h>
|
|
#include <cppbor_parse.h>
|
|
#include <future>
|
|
#include <tuple>
|
|
|
|
#include <aidl/android/hardware/security/keymint/HardwareAuthToken.h>
|
|
#include <aidl/android/hardware/security/secureclock/TimeStampToken.h>
|
|
#include <aidl/android/security/authorization/AuthorizationTokens.h>
|
|
#include <aidl/android/security/authorization/IKeystoreAuthorization.h>
|
|
|
|
#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<IIdentityCredentialStore> halStoreBinder,
|
|
sp<IPresentationSession> 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<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 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<IIdentityCredential> 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<uint8_t>* _aidl_return) {
|
|
if (halSessionBinder_) {
|
|
return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC,
|
|
"Cannot be used with session");
|
|
}
|
|
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, bool allowUsingExpiredKeys,
|
|
bool incrementUsageCount, int64_t* _aidl_return) {
|
|
if (halSessionBinder_) {
|
|
return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC,
|
|
"Cannot be used with session");
|
|
}
|
|
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");
|
|
}
|
|
|
|
// 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<uint8_t>& requestMessage,
|
|
const vector<RequestNamespaceParcel>& requestNamespaces,
|
|
const vector<uint8_t>& sessionTranscript,
|
|
const vector<uint8_t>& readerSignature, bool allowUsingExhaustedKeys,
|
|
bool allowUsingExpiredKeys, bool incrementUsageCount,
|
|
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");
|
|
}
|
|
|
|
// If used in a session, get the binder on demand...
|
|
//
|
|
sp<IIdentityCredential> 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<int32_t> requestCounts;
|
|
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
|
|
// 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<bool> 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<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;
|
|
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<SecureAccessControlProfile> 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<RequestNamespace> halRequestNamespaces;
|
|
for (const RequestNamespaceParcel& rns : requestNamespaces) {
|
|
RequestNamespace ns;
|
|
ns.namespaceName = rns.namespaceName;
|
|
for (const RequestEntryParcel& rep : rns.entries) {
|
|
optional<EntryData> 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<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, 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<uint8_t> value;
|
|
for (const auto& encryptedChunk : eData.value().encryptedChunks) {
|
|
vector<uint8_t> 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<uint8_t>* _aidl_return) {
|
|
if (halSessionBinder_) {
|
|
return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC,
|
|
"Cannot be used with session");
|
|
}
|
|
|
|
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()) {
|
|
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");
|
|
}
|
|
|
|
if (halSessionBinder_) {
|
|
return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC,
|
|
"Cannot be used with session");
|
|
}
|
|
|
|
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");
|
|
}
|
|
|
|
if (halSessionBinder_) {
|
|
return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC,
|
|
"Cannot be used with session");
|
|
}
|
|
|
|
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) {
|
|
if (halSessionBinder_) {
|
|
return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC,
|
|
"Cannot be used with session");
|
|
}
|
|
|
|
vector<uint8_t> 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<vector<uint8_t>> 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<uint8_t>& 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<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, minValidTimeMillis);
|
|
if (!data->saveToDisk()) {
|
|
return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC,
|
|
"Error saving data");
|
|
}
|
|
return Status::ok();
|
|
}
|
|
|
|
Status Credential::getAuthKeysNeedingCertification(vector<AuthKeyParcel>* _aidl_return) {
|
|
if (halSessionBinder_) {
|
|
return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC,
|
|
"Cannot be used with session");
|
|
}
|
|
|
|
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_);
|
|
if (!keysNeedingCert) {
|
|
return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC,
|
|
"Error getting auth keys neededing certification");
|
|
}
|
|
vector<AuthKeyParcel> authKeyParcels;
|
|
for (const vector<uint8_t>& 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<uint8_t>& staticAuthData) {
|
|
if (halSessionBinder_) {
|
|
return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC,
|
|
"Cannot be used with session");
|
|
}
|
|
|
|
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()) {
|
|
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 (halSessionBinder_) {
|
|
return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC,
|
|
"Cannot be used with session");
|
|
}
|
|
|
|
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");
|
|
}
|
|
return Status::ok();
|
|
}
|
|
|
|
Status Credential::getAuthenticationDataUsageCount(vector<int32_t>* _aidl_return) {
|
|
if (halSessionBinder_) {
|
|
return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC,
|
|
"Cannot be used with session");
|
|
}
|
|
|
|
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);
|
|
}
|
|
*_aidl_return = ret;
|
|
return Status::ok();
|
|
}
|
|
|
|
Status Credential::getAuthenticationDataExpirations(vector<int64_t>* _aidl_return) {
|
|
if (halSessionBinder_) {
|
|
return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC,
|
|
"Cannot be used with session");
|
|
}
|
|
|
|
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<int64_t> 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<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");
|
|
}
|
|
|
|
if (halSessionBinder_) {
|
|
return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC,
|
|
"Cannot be used with session");
|
|
}
|
|
|
|
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);
|
|
|
|
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
|