045a2c87e0
Also fix a bug so the same AuthKey is used for several getEntries() calls on a credential. This matches the behavior in the Jetpack. Bug: 197965513 Test: New CTS tests and new screen in CtsVerifier Change-Id: I344f44b5655f0977ee650b518ce669e3c8a7b47a
918 lines
41 KiB
C++
918 lines
41 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);
|
|
}
|
|
|
|
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) {
|
|
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);
|
|
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 (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");
|
|
}
|
|
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();
|
|
}
|
|
|
|
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] = data->getAvailableAuthenticationKeys();
|
|
writableCredential->setAvailableAuthenticationKeys(keyCount, maxUsesPerKey);
|
|
|
|
// 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
|