472e6c8e18
- Add Credential.proveOwership() - Add Credential.deleteWithChallenge() - Add Credential.updateCredential() - Add Credential.storeStaticAuthenticationDataWithExpirationDate() - Store this on disk. For entries stored without this parameter assume they never expire. - Add allowUsingExpiredKeys to Credential.selectAuthKey() and Credential.getEntries() - Unless set to true, never select an expired key - Introduce ERROR_NOT_SUPPORTED and return this if HAL does not support operation Bug: 170146643 Test: atest android.security.identity.cts Change-Id: Ic5dafc6498c9c59b82942def9d348d974f008589
279 lines
11 KiB
C++
279 lines
11 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 "WritableCredential"
|
|
|
|
#include <android-base/logging.h>
|
|
#include <android/hardware/identity/support/IdentityCredentialSupport.h>
|
|
#include <android/security/identity/ICredentialStore.h>
|
|
#include <binder/IPCThreadState.h>
|
|
#include <cppbor.h>
|
|
#include <cppbor_parse.h>
|
|
#include <keystore/keystore_attestation_id.h>
|
|
|
|
#include "CredentialData.h"
|
|
#include "Util.h"
|
|
#include "WritableCredential.h"
|
|
|
|
namespace android {
|
|
namespace security {
|
|
namespace identity {
|
|
|
|
using ::std::pair;
|
|
|
|
using ::android::hardware::identity::SecureAccessControlProfile;
|
|
|
|
using ::android::hardware::identity::support::chunkVector;
|
|
|
|
WritableCredential::WritableCredential(const string& dataPath, const string& credentialName,
|
|
const string& docType, bool isUpdate,
|
|
HardwareInformation hwInfo,
|
|
sp<IWritableIdentityCredential> halBinder, int halApiVersion)
|
|
: dataPath_(dataPath), credentialName_(credentialName), docType_(docType), isUpdate_(isUpdate),
|
|
hwInfo_(std::move(hwInfo)), halBinder_(halBinder), halApiVersion_(halApiVersion) {}
|
|
|
|
WritableCredential::~WritableCredential() {}
|
|
|
|
void WritableCredential::setCredentialUpdatedCallback(
|
|
std::function<void()>&& onCredentialUpdatedCallback) {
|
|
onCredentialUpdatedCallback_ = onCredentialUpdatedCallback;
|
|
}
|
|
|
|
Status WritableCredential::ensureAttestationCertificateExists(const vector<uint8_t>& challenge) {
|
|
if (!attestationCertificate_.empty()) {
|
|
return Status::ok();
|
|
}
|
|
|
|
const int32_t callingUid = IPCThreadState::self()->getCallingUid();
|
|
auto asn1AttestationId = android::security::gather_attestation_application_id(callingUid);
|
|
if (!asn1AttestationId.isOk()) {
|
|
LOG(ERROR) << "Failed gathering AttestionApplicationId";
|
|
return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC,
|
|
"Failed gathering AttestionApplicationId");
|
|
}
|
|
|
|
vector<Certificate> certificateChain;
|
|
Status status = halBinder_->getAttestationCertificate(asn1AttestationId.value(), challenge,
|
|
&certificateChain);
|
|
if (!status.isOk()) {
|
|
LOG(ERROR) << "Error calling getAttestationCertificate()";
|
|
return halStatusToGenericError(status);
|
|
}
|
|
|
|
vector<vector<uint8_t>> splitCerts;
|
|
for (const auto& cert : certificateChain) {
|
|
splitCerts.push_back(cert.encodedCertificate);
|
|
}
|
|
attestationCertificate_ =
|
|
::android::hardware::identity::support::certificateChainJoin(splitCerts);
|
|
|
|
return Status::ok();
|
|
}
|
|
|
|
Status WritableCredential::getCredentialKeyCertificateChain(const vector<uint8_t>& challenge,
|
|
vector<uint8_t>* _aidl_return) {
|
|
if (isUpdate_) {
|
|
return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC,
|
|
"Cannot be called for an update");
|
|
}
|
|
Status ensureStatus = ensureAttestationCertificateExists(challenge);
|
|
if (!ensureStatus.isOk()) {
|
|
return ensureStatus;
|
|
}
|
|
|
|
*_aidl_return = attestationCertificate_;
|
|
return Status::ok();
|
|
}
|
|
|
|
void WritableCredential::setAttestationCertificate(const vector<uint8_t>& attestationCertificate) {
|
|
attestationCertificate_ = attestationCertificate;
|
|
}
|
|
|
|
void WritableCredential::setAvailableAuthenticationKeys(int keyCount, int maxUsesPerKey) {
|
|
keyCount_ = keyCount;
|
|
maxUsesPerKey_ = maxUsesPerKey;
|
|
}
|
|
|
|
ssize_t WritableCredential::calcExpectedProofOfProvisioningSize(
|
|
const vector<AccessControlProfileParcel>& accessControlProfiles,
|
|
const vector<EntryNamespaceParcel>& entryNamespaces) {
|
|
|
|
// Right now, we calculate the size by simply just calculating the
|
|
// CBOR. There's a little bit of overhead associated with this (as compared
|
|
// to just adding up sizes) but it's a lot simpler and robust. In the future
|
|
// if this turns out to be a problem, we can optimize it.
|
|
//
|
|
|
|
cppbor::Array acpArray;
|
|
for (const AccessControlProfileParcel& profile : accessControlProfiles) {
|
|
cppbor::Map map;
|
|
map.add("id", profile.id);
|
|
if (profile.readerCertificate.size() > 0) {
|
|
map.add("readerCertificate", cppbor::Bstr(profile.readerCertificate));
|
|
}
|
|
if (profile.userAuthenticationRequired) {
|
|
map.add("userAuthenticationRequired", profile.userAuthenticationRequired);
|
|
map.add("timeoutMillis", profile.userAuthenticationTimeoutMillis);
|
|
}
|
|
acpArray.add(std::move(map));
|
|
}
|
|
|
|
cppbor::Map dataMap;
|
|
for (const EntryNamespaceParcel& ensParcel : entryNamespaces) {
|
|
cppbor::Array entriesArray;
|
|
for (const EntryParcel& eParcel : ensParcel.entries) {
|
|
// TODO: ideally do do this without parsing the data (but still validate data is valid
|
|
// CBOR).
|
|
auto [itemForValue, _, _2] = cppbor::parse(eParcel.value);
|
|
if (itemForValue == nullptr) {
|
|
return -1;
|
|
}
|
|
cppbor::Map entryMap;
|
|
entryMap.add("name", eParcel.name);
|
|
entryMap.add("value", std::move(itemForValue));
|
|
cppbor::Array acpIdsArray;
|
|
for (int32_t id : eParcel.accessControlProfileIds) {
|
|
acpIdsArray.add(id);
|
|
}
|
|
entryMap.add("accessControlProfiles", std::move(acpIdsArray));
|
|
entriesArray.add(std::move(entryMap));
|
|
}
|
|
dataMap.add(ensParcel.namespaceName, std::move(entriesArray));
|
|
}
|
|
|
|
cppbor::Array array;
|
|
array.add("ProofOfProvisioning");
|
|
array.add(docType_);
|
|
array.add(std::move(acpArray));
|
|
array.add(std::move(dataMap));
|
|
array.add(false); // testCredential
|
|
return array.encode().size();
|
|
}
|
|
|
|
Status
|
|
WritableCredential::personalize(const vector<AccessControlProfileParcel>& accessControlProfiles,
|
|
const vector<EntryNamespaceParcel>& entryNamespaces,
|
|
int64_t secureUserId, vector<uint8_t>* _aidl_return) {
|
|
if (!isUpdate_) {
|
|
Status ensureStatus =
|
|
ensureAttestationCertificateExists({0x00}); // Challenge cannot be empty.
|
|
if (!ensureStatus.isOk()) {
|
|
return ensureStatus;
|
|
}
|
|
}
|
|
|
|
uid_t callingUid = android::IPCThreadState::self()->getCallingUid();
|
|
CredentialData data = CredentialData(dataPath_, callingUid, credentialName_);
|
|
|
|
// Note: The value 0 is used to convey that no user-authentication is needed for this
|
|
// credential. This is to allow creating credentials w/o user authentication on devices
|
|
// where Secure lock screen is not enabled.
|
|
data.setSecureUserId(secureUserId);
|
|
|
|
data.setAttestationCertificate(attestationCertificate_);
|
|
|
|
vector<int32_t> entryCounts;
|
|
for (const EntryNamespaceParcel& ensParcel : entryNamespaces) {
|
|
entryCounts.push_back(ensParcel.entries.size());
|
|
}
|
|
|
|
ssize_t expectedPoPSize =
|
|
calcExpectedProofOfProvisioningSize(accessControlProfiles, entryNamespaces);
|
|
if (expectedPoPSize < 0) {
|
|
return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC,
|
|
"Data is not valid CBOR");
|
|
}
|
|
// This is not catastrophic, we might be dealing with a version 1 implementation which
|
|
// doesn't have this method.
|
|
Status status = halBinder_->setExpectedProofOfProvisioningSize(expectedPoPSize);
|
|
if (!status.isOk()) {
|
|
LOG(INFO) << "Failed setting expected ProofOfProvisioning size, assuming V1 HAL "
|
|
<< "and continuing";
|
|
}
|
|
|
|
status = halBinder_->startPersonalization(accessControlProfiles.size(), entryCounts);
|
|
if (!status.isOk()) {
|
|
return halStatusToGenericError(status);
|
|
}
|
|
|
|
for (const AccessControlProfileParcel& acpParcel : accessControlProfiles) {
|
|
Certificate certificate;
|
|
certificate.encodedCertificate = acpParcel.readerCertificate;
|
|
SecureAccessControlProfile profile;
|
|
status = halBinder_->addAccessControlProfile(
|
|
acpParcel.id, certificate, acpParcel.userAuthenticationRequired,
|
|
acpParcel.userAuthenticationTimeoutMillis, secureUserId, &profile);
|
|
if (!status.isOk()) {
|
|
return halStatusToGenericError(status);
|
|
}
|
|
data.addSecureAccessControlProfile(profile);
|
|
}
|
|
|
|
for (const EntryNamespaceParcel& ensParcel : entryNamespaces) {
|
|
for (const EntryParcel& eParcel : ensParcel.entries) {
|
|
vector<vector<uint8_t>> chunks = chunkVector(eParcel.value, hwInfo_.dataChunkSize);
|
|
|
|
vector<int32_t> ids;
|
|
std::copy(eParcel.accessControlProfileIds.begin(),
|
|
eParcel.accessControlProfileIds.end(), std::back_inserter(ids));
|
|
|
|
status = halBinder_->beginAddEntry(ids, ensParcel.namespaceName, eParcel.name,
|
|
eParcel.value.size());
|
|
if (!status.isOk()) {
|
|
return halStatusToGenericError(status);
|
|
}
|
|
|
|
vector<vector<uint8_t>> encryptedChunks;
|
|
for (const auto& chunk : chunks) {
|
|
vector<uint8_t> encryptedChunk;
|
|
status = halBinder_->addEntryValue(chunk, &encryptedChunk);
|
|
if (!status.isOk()) {
|
|
return halStatusToGenericError(status);
|
|
}
|
|
encryptedChunks.push_back(encryptedChunk);
|
|
}
|
|
EntryData eData;
|
|
eData.size = eParcel.value.size();
|
|
eData.accessControlProfileIds = std::move(ids);
|
|
eData.encryptedChunks = std::move(encryptedChunks);
|
|
data.addEntryData(ensParcel.namespaceName, eParcel.name, eData);
|
|
}
|
|
}
|
|
|
|
vector<uint8_t> credentialData;
|
|
vector<uint8_t> proofOfProvisioningSignature;
|
|
status = halBinder_->finishAddingEntries(&credentialData, &proofOfProvisioningSignature);
|
|
if (!status.isOk()) {
|
|
return halStatusToGenericError(status);
|
|
}
|
|
data.setCredentialData(credentialData);
|
|
|
|
data.setAvailableAuthenticationKeys(keyCount_, maxUsesPerKey_);
|
|
|
|
if (!data.saveToDisk()) {
|
|
return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC,
|
|
"Error saving credential data to disk");
|
|
}
|
|
|
|
onCredentialUpdatedCallback_();
|
|
|
|
*_aidl_return = proofOfProvisioningSignature;
|
|
return Status::ok();
|
|
}
|
|
|
|
} // namespace identity
|
|
} // namespace security
|
|
} // namespace android
|