Merge "Confirmationui Rate Limiting App Abort Bug Fix"
diff --git a/identity/Android.bp b/identity/Android.bp
new file mode 100644
index 0000000..240e107
--- /dev/null
+++ b/identity/Android.bp
@@ -0,0 +1,86 @@
+cc_defaults {
+ name: "identity_defaults",
+ cflags: [
+ "-Wall",
+ // "-Werror",
+ "-Wextra",
+ "-Wunused",
+ ],
+ sanitize: {
+ misc_undefined : ["integer"],
+ },
+ clang : true,
+}
+
+cc_binary {
+ name: "credstore",
+ defaults: ["identity_defaults"],
+
+ srcs: [
+ "main.cpp",
+ "CredentialStore.cpp",
+ "CredentialStoreFactory.cpp",
+ "WritableCredential.cpp",
+ "Credential.cpp",
+ "CredentialData.cpp",
+ "Util.cpp",
+ ],
+ init_rc: ["credstore.rc"],
+ shared_libs: [
+ "android.hardware.identity@1.0",
+ "android.hardware.keymaster@4.0",
+ "libbase",
+ "libbinder",
+ "libkeystore_aidl",
+ "libcredstore_aidl",
+ "libutils",
+ "libhidlbase",
+ "android.hardware.identity-support-lib",
+ "libkeymaster4support",
+ ],
+ static_libs: [
+ "libcppbor",
+ ]
+}
+
+filegroup {
+ name: "credstore_aidl",
+ srcs: [
+ "binder/android/security/identity/ICredential.aidl",
+ "binder/android/security/identity/IWritableCredential.aidl",
+ "binder/android/security/identity/ICredentialStore.aidl",
+ "binder/android/security/identity/AccessControlProfileParcel.aidl",
+ "binder/android/security/identity/EntryNamespaceParcel.aidl",
+ "binder/android/security/identity/EntryParcel.aidl",
+ "binder/android/security/identity/RequestNamespaceParcel.aidl",
+ "binder/android/security/identity/RequestEntryParcel.aidl",
+ "binder/android/security/identity/ResultNamespaceParcel.aidl",
+ "binder/android/security/identity/ResultEntryParcel.aidl",
+ "binder/android/security/identity/GetEntriesResultParcel.aidl",
+ "binder/android/security/identity/AuthKeyParcel.aidl",
+ "binder/android/security/identity/SecurityHardwareInfoParcel.aidl",
+ "binder/android/security/identity/ICredentialStoreFactory.aidl",
+ ],
+ path: "binder",
+}
+
+cc_library_shared {
+ name: "libcredstore_aidl",
+ srcs: [
+ ":credstore_aidl",
+ ],
+ aidl: {
+ export_aidl_headers: true,
+ include_dirs: [
+ "system/security/identity/binder",
+ ],
+ },
+ shared_libs: [
+ "libbinder",
+ "libutils",
+ "libkeymaster4support",
+ ],
+ export_shared_lib_headers: [
+ "libbinder",
+ ],
+}
diff --git a/identity/Credential.cpp b/identity/Credential.cpp
new file mode 100644
index 0000000..6b03309
--- /dev/null
+++ b/identity/Credential.cpp
@@ -0,0 +1,494 @@
+/*
+ * 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 "Credential"
+
+#include <android-base/logging.h>
+
+#include <android/hardware/identity/support/IdentityCredentialSupport.h>
+
+#include <android/security/identity/ICredentialStore.h>
+
+#include <android/security/keystore/IKeystoreService.h>
+#include <binder/IPCThreadState.h>
+#include <binder/IServiceManager.h>
+#include <keymasterV4_0/keymaster_utils.h>
+
+#include <cppbor.h>
+#include <cppbor_parse.h>
+
+#include "Credential.h"
+#include "CredentialData.h"
+#include "Util.h"
+
+namespace android {
+namespace security {
+namespace identity {
+
+using std::optional;
+
+using android::security::keystore::IKeystoreService;
+
+using ::android::hardware::hidl_vec;
+
+using ::android::hardware::identity::V1_0::Result;
+using ::android::hardware::identity::V1_0::ResultCode;
+using ::android::hardware::identity::V1_0::SecureAccessControlProfile;
+
+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::V4_0::HardwareAuthToken;
+
+Credential::Credential(const std::string& dataPath, const std::string& credentialName)
+ : dataPath_(dataPath), credentialName_(credentialName) {}
+
+Credential::~Credential() {}
+
+Status Credential::loadCredential(sp<IIdentityCredentialStore> halStoreBinder) {
+ uid_t callingUid = android::IPCThreadState::self()->getCallingUid();
+ sp<CredentialData> data = new CredentialData(dataPath_, callingUid, credentialName_);
+ if (!data->loadFromDisk()) {
+ LOG(ERROR) << "Error loading data for credential";
+ return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC,
+ "Error loading data for credential");
+ }
+
+ data_ = data;
+
+ Result result;
+ sp<IIdentityCredential> halBinder;
+ halStoreBinder->getCredential(
+ data_->getCredentialData(),
+ [&](const Result& _result, const sp<IIdentityCredential>& _halBinder) {
+ result = _result;
+ halBinder = _halBinder;
+ });
+ if (result.code != ResultCode::OK) {
+ LOG(ERROR) << "Error getting HAL binder";
+ return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC);
+ }
+
+ halBinder_ = halBinder;
+
+ return Status::ok();
+}
+
+Status Credential::getCredentialKeyCertificateChain(std::vector<uint8_t>* _aidl_return) {
+ *_aidl_return = data_->getAttestationCertificate();
+ return Status::ok();
+}
+
+// Returns operation handle
+Status Credential::selectAuthKey(bool allowUsingExhaustedKeys, int64_t* _aidl_return) {
+
+ selectedAuthKey_ = data_->selectAuthKey(allowUsingExhaustedKeys);
+ if (selectedAuthKey_ == nullptr) {
+ return Status::fromServiceSpecificError(
+ ICredentialStore::ERROR_NO_AUTHENTICATION_KEY_AVAILABLE,
+ "No suitable authentication key available");
+ }
+
+ Result result;
+ uint64_t challenge;
+ halBinder_->createAuthChallenge([&](const Result& _result, uint64_t _challenge) {
+ result = _result;
+ challenge = _challenge;
+ });
+ if (result.code != ResultCode::OK) {
+ LOG(ERROR) << "createAuthChallenge() failed " << ((int)result.code) << ": "
+ << result.message;
+ return halResultToGenericError(result);
+ }
+ if (challenge == 0) {
+ return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC,
+ "Returned challenge is 0 (bug in HAL or TA)");
+ }
+
+ selectedChallenge_ = challenge;
+ *_aidl_return = challenge;
+ return Status::ok();
+}
+
+// Returns false if an error occurred communicating with keystore.
+//
+// Sets |authToken| to the empty vector if an auth token couldn't be obtained.
+//
+bool getAuthTokenFromKeystore(uint64_t challenge, uint64_t secureUserId,
+ unsigned int authTokenMaxAgeMillis, vector<uint8_t>& authToken) {
+ sp<IServiceManager> sm = defaultServiceManager();
+ sp<IBinder> binder = sm->getService(String16("android.security.keystore"));
+ sp<IKeystoreService> keystore = interface_cast<IKeystoreService>(binder);
+ if (keystore == nullptr) {
+ return false;
+ }
+
+ vector<uint8_t> returnedAuthToken;
+ Status ret = keystore->getAuthTokenForCredstore(challenge, secureUserId, authTokenMaxAgeMillis,
+ &returnedAuthToken);
+ if (!ret.isOk()) {
+ return false;
+ }
+ authToken = returnedAuthToken;
+ return true;
+}
+
+Status Credential::getEntries(const vector<uint8_t>& requestMessage,
+ const vector<RequestNamespaceParcel>& requestNamespaces,
+ const vector<uint8_t>& sessionTranscript,
+ const vector<uint8_t>& readerSignature, bool allowUsingExhaustedKeys,
+ GetEntriesResultParcel* _aidl_return) {
+ GetEntriesResultParcel ret;
+
+ // 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<uint16_t> requestCounts;
+ const vector<SecureAccessControlProfile>& allProfiles = data_->getSecureAccessControlProfiles();
+ vector<bool> includeProfile(allProfiles.size());
+ 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> data = data_->getEntryData(rns.namespaceName, rep.name);
+ if (data) {
+ for (uint16_t id : data.value().accessControlProfileIds) {
+ if (id >= includeProfile.size()) {
+ LOG(ERROR) << "Invalid accessControlProfileId " << id << " for "
+ << rns.namespaceName << ": " << rep.name;
+ return Status::fromServiceSpecificError(
+ ICredentialStore::ERROR_GENERIC, "Invalid accessProfileId for entry");
+ }
+ 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[n]) {
+ 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;
+ }
+ }
+ }
+ }
+
+ // If requesting a challenge-based authToken the idea is that authentication
+ // happens as part of the transaction. As such, authTokenMaxAgeMillis should
+ // be nearly zero. We'll use 10 seconds for this.
+ if (userAuthNeeded && selectedChallenge_ != 0) {
+ authTokenMaxAgeMillis = 10 * 1000;
+ }
+
+ // Only get an authToken if it's actually needed.
+ HardwareAuthToken authToken;
+ if (userAuthNeeded) {
+ vector<uint8_t> authTokenBytes;
+ if (!getAuthTokenFromKeystore(selectedChallenge_, data_->getSecureUserId(),
+ authTokenMaxAgeMillis, authTokenBytes)) {
+ LOG(ERROR) << "Error getting auth token from keystore";
+ return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC,
+ "Error getting auth token from keystore");
+ }
+ if (authTokenBytes.size() > 0) {
+ authToken =
+ android::hardware::keymaster::V4_0::support::hidlVec2AuthToken(authTokenBytes);
+ }
+ }
+
+ Result result;
+ halBinder_->startRetrieval(selectedProfiles, authToken, requestMessage, sessionTranscript,
+ readerSignature, requestCounts,
+ [&](const Result& _result) { result = _result; });
+ if (result.code == ResultCode::EPHEMERAL_PUBLIC_KEY_NOT_FOUND) {
+ LOG(ERROR) << "startRetrieval() failed " << ((int)result.code) << ": " << result.message;
+ return Status::fromServiceSpecificError(
+ ICredentialStore::ERROR_EPHEMERAL_PUBLIC_KEY_NOT_FOUND, result.message.c_str());
+ } else if (result.code == ResultCode::READER_SIGNATURE_CHECK_FAILED) {
+ LOG(ERROR) << "startRetrieval() failed " << ((int)result.code) << ": " << result.message;
+ return Status::fromServiceSpecificError(ICredentialStore::ERROR_INVALID_READER_SIGNATURE,
+ result.message.c_str());
+ } else if (result.code == ResultCode::INVALID_ITEMS_REQUEST_MESSAGE) {
+ LOG(ERROR) << "startRetrieval() failed " << ((int)result.code) << ": " << result.message;
+ return Status::fromServiceSpecificError(
+ ICredentialStore::ERROR_INVALID_ITEMS_REQUEST_MESSAGE, result.message.c_str());
+ } else if (result.code == ResultCode::SESSION_TRANSCRIPT_MISMATCH) {
+ LOG(ERROR) << "startRetrieval() failed " << ((int)result.code) << ": " << result.message;
+ return Status::fromServiceSpecificError(ICredentialStore::ERROR_SESSION_TRANSCRIPT_MISMATCH,
+ result.message.c_str());
+ } else if (result.code != ResultCode::OK) {
+ LOG(ERROR) << "startRetrieval() failed " << ((int)result.code) << ": " << result.message;
+ return halResultToGenericError(result);
+ }
+
+ 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> data = data_->getEntryData(rns.namespaceName, rep.name);
+ if (!data) {
+ resultEntryParcel.status = STATUS_NO_SUCH_ENTRY;
+ resultNamespaceParcel.entries.push_back(resultEntryParcel);
+ continue;
+ }
+
+ halBinder_->startRetrieveEntryValue(rns.namespaceName, rep.name, data.value().size,
+ data.value().accessControlProfileIds,
+ [&](const Result& _result) { result = _result; });
+ if (result.code == ResultCode::USER_AUTHENTICATION_FAILED) {
+ resultEntryParcel.status = STATUS_USER_AUTHENTICATION_FAILED;
+ resultNamespaceParcel.entries.push_back(resultEntryParcel);
+ continue;
+ } else if (result.code == ResultCode::READER_AUTHENTICATION_FAILED) {
+ resultEntryParcel.status = STATUS_READER_AUTHENTICATION_FAILED;
+ resultNamespaceParcel.entries.push_back(resultEntryParcel);
+ continue;
+ } else if (result.code == ResultCode::NOT_IN_REQUEST_MESSAGE) {
+ resultEntryParcel.status = STATUS_NOT_IN_REQUEST_MESSAGE;
+ resultNamespaceParcel.entries.push_back(resultEntryParcel);
+ continue;
+ } else if (result.code == ResultCode::NO_ACCESS_CONTROL_PROFILES) {
+ resultEntryParcel.status = STATUS_NO_ACCESS_CONTROL_PROFILES;
+ resultNamespaceParcel.entries.push_back(resultEntryParcel);
+ continue;
+ } else if (result.code != ResultCode::OK) {
+ LOG(ERROR) << "startRetrieveEntryValue() failed " << ((int)result.code) << ": "
+ << result.message;
+ return halResultToGenericError(result);
+ }
+
+ vector<uint8_t> value;
+ for (const auto& encryptedChunk : data.value().encryptedChunks) {
+ halBinder_->retrieveEntryValue(
+ encryptedChunk, [&](const Result& _result, const hidl_vec<uint8_t>& chunk) {
+ result = _result;
+ value.insert(value.end(), chunk.begin(), chunk.end());
+ });
+ if (result.code != ResultCode::OK) {
+ LOG(ERROR) << "retrieveEntryValue failed() " << ((int)result.code) << ": "
+ << result.message;
+ return halResultToGenericError(result);
+ }
+ }
+
+ resultEntryParcel.status = STATUS_OK;
+ resultEntryParcel.value = value;
+ resultNamespaceParcel.entries.push_back(resultEntryParcel);
+ }
+ ret.resultNamespaces.push_back(resultNamespaceParcel);
+ }
+
+ // 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 = selectedAuthKey_;
+ if (sessionTranscript.size() > 0) {
+ if (authKey == nullptr) {
+ authKey = data_->selectAuthKey(allowUsingExhaustedKeys);
+ if (authKey == nullptr) {
+ return Status::fromServiceSpecificError(
+ ICredentialStore::ERROR_NO_AUTHENTICATION_KEY_AVAILABLE,
+ "No suitable authentication key available");
+ }
+ }
+ }
+
+ vector<uint8_t> signingKeyBlob;
+ if (authKey != nullptr) {
+ signingKeyBlob = authKey->keyBlob;
+ }
+ halBinder_->finishRetrieval(signingKeyBlob, [&](const Result& _result,
+ const hidl_vec<uint8_t>& _mac,
+ const hidl_vec<uint8_t>& _deviceNameSpaces) {
+ result = _result;
+ ret.mac = _mac;
+ ret.deviceNameSpaces = _deviceNameSpaces;
+ if (authKey != nullptr) {
+ ret.staticAuthenticationData = authKey->staticAuthenticationData;
+ }
+ });
+ if (result.code != ResultCode::OK) {
+ LOG(ERROR) << "finishRetrieval failed() " << ((int)result.code) << ": " << result.message;
+ return halResultToGenericError(result);
+ }
+
+ // Ensure useCount is updated on disk.
+ if (authKey != nullptr) {
+ 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) {
+ Result result;
+ halBinder_->deleteCredential(
+ [&](const Result& _result, const hidl_vec<uint8_t>& _proofOfDeletionSignature) {
+ result = _result;
+ *_aidl_return = _proofOfDeletionSignature;
+ });
+ if (result.code != ResultCode::OK) {
+ LOG(ERROR) << "deleteCredential failed() " << ((int)result.code) << ": " << result.message;
+ return halResultToGenericError(result);
+ }
+ if (!data_->deleteCredential()) {
+ return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC,
+ "Error deleting credential data on disk");
+ }
+ return Status::ok();
+}
+
+Status Credential::createEphemeralKeyPair(vector<uint8_t>* _aidl_return) {
+ Result result;
+
+ vector<uint8_t> keyPair;
+ halBinder_->createEphemeralKeyPair(
+ [&](const Result& _result, const hidl_vec<uint8_t>& _keyPair) {
+ result = _result;
+ keyPair = _keyPair;
+ });
+ if (result.code != ResultCode::OK) {
+ LOG(ERROR) << "createEphemeralKeyPair failed() " << ((int)result.code) << ": "
+ << result.message;
+ return halResultToGenericError(result);
+ }
+
+ optional<vector<uint8_t>> pkcs12Bytes = ecKeyPairGetPkcs12(keyPair,
+ "ephemeralKey", // Alias for key
+ "0", // Serial, as a decimal number
+ "Credstore", // Issuer
+ "Ephemeral Key", // Subject
+ 0, // Validity Not Before
+ 24 * 60 * 60); // Validity Not After
+ 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) {
+ Result result;
+ halBinder_->setReaderEphemeralPublicKey(publicKey,
+ [&](const Result& _result) { result = _result; });
+ if (result.code != ResultCode::OK) {
+ LOG(ERROR) << "setReaderEphemeralPublicKey failed() " << ((int)result.code) << ": "
+ << result.message;
+ return halResultToGenericError(result);
+ }
+ return Status::ok();
+}
+
+Status Credential::setAvailableAuthenticationKeys(int32_t keyCount, int32_t maxUsesPerKey) {
+ 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) {
+ 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 (!data_->storeStaticAuthenticationData(authenticationKey.x509cert, 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) {
+ 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();
+}
+
+} // namespace identity
+} // namespace security
+} // namespace android
diff --git a/identity/Credential.h b/identity/Credential.h
new file mode 100644
index 0000000..356d75f
--- /dev/null
+++ b/identity/Credential.h
@@ -0,0 +1,88 @@
+/*
+ * 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.
+ */
+
+#ifndef SYSTEM_SECURITY_CREDENTIAL_H_
+#define SYSTEM_SECURITY_CREDENTIAL_H_
+
+#include <string>
+#include <vector>
+
+#include <android/security/identity/BnCredential.h>
+
+#include <android/hardware/identity/1.0/IIdentityCredentialStore.h>
+#include <android/hardware/identity/1.0/types.h>
+
+#include "CredentialData.h"
+
+namespace android {
+namespace security {
+namespace identity {
+
+using ::android::sp;
+using ::android::binder::Status;
+using ::std::string;
+using ::std::vector;
+
+using ::android::hardware::identity::V1_0::IIdentityCredential;
+using ::android::hardware::identity::V1_0::IIdentityCredentialStore;
+
+class Credential : public BnCredential {
+ public:
+ Credential(const string& dataPath, const string& credentialName);
+ ~Credential();
+
+ Status loadCredential(sp<IIdentityCredentialStore> halStoreBinder);
+
+ // ICredential overrides
+ Status createEphemeralKeyPair(vector<uint8_t>* _aidl_return) override;
+
+ Status setReaderEphemeralPublicKey(const vector<uint8_t>& publicKey) override;
+
+ Status deleteCredential(vector<uint8_t>* _aidl_return) override;
+
+ Status getCredentialKeyCertificateChain(vector<uint8_t>* _aidl_return) override;
+
+ Status selectAuthKey(bool allowUsingExhaustedKeys, int64_t* _aidl_return) override;
+
+ Status getEntries(const vector<uint8_t>& requestMessage,
+ const vector<RequestNamespaceParcel>& requestNamespaces,
+ const vector<uint8_t>& sessionTranscript,
+ const vector<uint8_t>& readerSignature, bool allowUsingExhaustedKeys,
+ GetEntriesResultParcel* _aidl_return) override;
+
+ Status setAvailableAuthenticationKeys(int32_t keyCount, int32_t maxUsesPerKey) override;
+ Status getAuthKeysNeedingCertification(vector<AuthKeyParcel>* _aidl_return) override;
+ Status storeStaticAuthenticationData(const AuthKeyParcel& authenticationKey,
+ const vector<uint8_t>& staticAuthData) override;
+ Status getAuthenticationDataUsageCount(vector<int32_t>* _aidl_return) override;
+
+ private:
+ string dataPath_;
+ string credentialName_;
+
+ const AuthKeyData* selectedAuthKey_ = nullptr;
+ uint64_t selectedChallenge_ = 0;
+
+ sp<CredentialData> data_;
+
+ sp<IIdentityCredential> halBinder_;
+};
+
+} // namespace identity
+} // namespace security
+} // namespace android
+
+#endif // SYSTEM_SECURITY_IDENTITY_CREDENTIAL_H_
diff --git a/identity/CredentialData.cpp b/identity/CredentialData.cpp
new file mode 100644
index 0000000..99dd04b
--- /dev/null
+++ b/identity/CredentialData.cpp
@@ -0,0 +1,568 @@
+/*
+ * 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 "CredentialData"
+
+#include <fcntl.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+
+#include <cppbor.h>
+#include <cppbor_parse.h>
+
+#include <android/hardware/identity/support/IdentityCredentialSupport.h>
+
+#include "CredentialData.h"
+#include "Util.h"
+
+namespace android {
+namespace security {
+namespace identity {
+
+using android::hardware::identity::V1_0::Result;
+using android::hardware::identity::V1_0::ResultCode;
+using std::optional;
+
+string CredentialData::calculateCredentialFileName(const string& dataPath, uid_t ownerUid,
+ const string& name) {
+ return android::base::StringPrintf(
+ "%s/%d-%s", dataPath.c_str(), (int)ownerUid,
+ android::hardware::identity::support::encodeHex(name).c_str());
+}
+
+CredentialData::CredentialData(const string& dataPath, uid_t ownerUid, const string& name)
+ : dataPath_(dataPath), ownerUid_(ownerUid), name_(name), secureUserId_(0) {
+ fileName_ = calculateCredentialFileName(dataPath_, ownerUid_, name_);
+}
+
+void CredentialData::setSecureUserId(int64_t secureUserId) {
+ secureUserId_ = secureUserId;
+}
+
+void CredentialData::setCredentialData(const vector<uint8_t>& credentialData) {
+ credentialData_ = credentialData;
+}
+
+void CredentialData::setAttestationCertificate(const vector<uint8_t>& attestationCertificate) {
+ attestationCertificate_ = attestationCertificate;
+}
+
+void CredentialData::addSecureAccessControlProfile(
+ const SecureAccessControlProfile& secureAccessControlProfile) {
+ secureAccessControlProfiles_.push_back(secureAccessControlProfile);
+}
+
+void CredentialData::addEntryData(const string& namespaceName, const string& entryName,
+ const EntryData& data) {
+ idToEncryptedChunks_[namespaceName + ":" + entryName] = data;
+}
+
+bool CredentialData::saveToDisk() const {
+ cppbor::Map map;
+
+ map.add("secureUserId", secureUserId_);
+
+ map.add("credentialData", credentialData_);
+
+ map.add("attestationCertificate", attestationCertificate_);
+
+ cppbor::Array sacpArray;
+ for (const SecureAccessControlProfile& sacp : secureAccessControlProfiles_) {
+ cppbor::Array array;
+ array.add(sacp.id);
+ array.add((const vector<uint8_t>&)sacp.readerCertificate);
+ array.add(sacp.userAuthenticationRequired);
+ array.add(sacp.timeoutMillis);
+ array.add(sacp.secureUserId);
+ vector<uint8_t> mac = sacp.mac;
+ array.add(mac);
+ sacpArray.add(std::move(array));
+ }
+ map.add("secureAccessControlProfiles", std::move(sacpArray));
+
+ cppbor::Map encryptedBlobsMap;
+ for (auto const& [nsAndName, entryData] : idToEncryptedChunks_) {
+ cppbor::Array encryptedChunkArray;
+ for (const vector<uint8_t>& encryptedChunk : entryData.encryptedChunks) {
+ encryptedChunkArray.add(encryptedChunk);
+ }
+ cppbor::Array entryDataArray;
+ entryDataArray.add(entryData.size);
+ cppbor::Array idsArray;
+ for (uint16_t id : entryData.accessControlProfileIds) {
+ idsArray.add(id);
+ }
+ entryDataArray.add(std::move(idsArray));
+ entryDataArray.add(std::move(encryptedChunkArray));
+ encryptedBlobsMap.add(nsAndName, std::move(entryDataArray));
+ }
+ map.add("entryData", std::move(encryptedBlobsMap));
+ map.add("authKeyCount", keyCount_);
+ map.add("maxUsesPerAuthKey", maxUsesPerKey_);
+
+ cppbor::Array authKeyDatasArray;
+ for (const AuthKeyData& data : authKeyDatas_) {
+ cppbor::Array array;
+ array.add(data.certificate);
+ array.add(data.keyBlob);
+ array.add(data.staticAuthenticationData);
+ array.add(data.pendingCertificate);
+ array.add(data.pendingKeyBlob);
+ array.add(data.useCount);
+ authKeyDatasArray.add(std::move(array));
+ }
+ map.add("authKeyData", std::move(authKeyDatasArray));
+
+ vector<uint8_t> credentialData = map.encode();
+
+ return fileSetContents(fileName_, credentialData);
+}
+
+optional<SecureAccessControlProfile> parseSacp(const cppbor::Item& item) {
+ const cppbor::Array* array = item.asArray();
+ if (array == nullptr || array->size() < 6) {
+ LOG(ERROR) << "The SACP CBOR is not an array with at least six elements (size="
+ << (array != nullptr ? array->size() : -1) << ")";
+ return {};
+ }
+ const cppbor::Int* itemId = ((*array)[0])->asInt();
+ const cppbor::Bstr* itemReaderCertificate = ((*array)[1])->asBstr();
+ const cppbor::Simple* simple = ((*array)[2])->asSimple();
+ const cppbor::Bool* itemUserAuthenticationRequired =
+ (simple != nullptr ? (simple->asBool()) : nullptr);
+ const cppbor::Int* itemTimeoutMillis = ((*array)[3])->asInt();
+ const cppbor::Int* itesecureUserId_ = ((*array)[4])->asInt();
+ const cppbor::Bstr* itemMac = ((*array)[5])->asBstr();
+ if (itemId == nullptr || itemReaderCertificate == nullptr ||
+ itemUserAuthenticationRequired == nullptr || itemTimeoutMillis == nullptr ||
+ itesecureUserId_ == nullptr || itemMac == nullptr) {
+ LOG(ERROR) << "One or more items SACP array in CBOR is of wrong type";
+ return {};
+ }
+ SecureAccessControlProfile sacp;
+ sacp.id = itemId->value();
+ sacp.readerCertificate = itemReaderCertificate->value();
+ sacp.userAuthenticationRequired = itemUserAuthenticationRequired->value();
+ sacp.timeoutMillis = itemTimeoutMillis->value();
+ sacp.secureUserId = itesecureUserId_->value();
+ sacp.mac = itemMac->value();
+ return sacp;
+}
+
+optional<AuthKeyData> parseAuthKeyData(const cppbor::Item& item) {
+ const cppbor::Array* array = item.asArray();
+ if (array == nullptr || array->size() < 6) {
+ LOG(ERROR) << "The AuthKeyData CBOR is not an array with at least six elements";
+ return {};
+ }
+ const cppbor::Bstr* itemCertificate = ((*array)[0])->asBstr();
+ const cppbor::Bstr* itemKeyBlob = ((*array)[1])->asBstr();
+ const cppbor::Bstr* itemStaticAuthenticationData = ((*array)[2])->asBstr();
+ const cppbor::Bstr* itemPendingCertificate = ((*array)[3])->asBstr();
+ const cppbor::Bstr* itemPendingKeyBlob = ((*array)[4])->asBstr();
+ const cppbor::Int* itemUseCount = ((*array)[5])->asInt();
+ if (itemCertificate == nullptr || itemKeyBlob == nullptr ||
+ itemStaticAuthenticationData == nullptr || itemPendingCertificate == nullptr ||
+ itemPendingKeyBlob == nullptr || itemUseCount == nullptr) {
+ LOG(ERROR) << "One or more items in AuthKeyData array in CBOR is of wrong type";
+ return {};
+ }
+ AuthKeyData authKeyData;
+ authKeyData.certificate = itemCertificate->value();
+ authKeyData.keyBlob = itemKeyBlob->value();
+ authKeyData.staticAuthenticationData = itemStaticAuthenticationData->value();
+ authKeyData.pendingCertificate = itemPendingCertificate->value();
+ authKeyData.pendingKeyBlob = itemPendingKeyBlob->value();
+ authKeyData.useCount = itemUseCount->value();
+ return authKeyData;
+}
+
+vector<uint16_t> parseAccessControlProfileIds(const cppbor::Item& item) {
+ const cppbor::Array* array = item.asArray();
+ if (array == nullptr) {
+ LOG(ERROR) << "The accessControlProfileIds member is not an array";
+ return {};
+ }
+
+ vector<uint16_t> accessControlProfileIds;
+ for (size_t n = 0; n < array->size(); n++) {
+ const cppbor::Int* itemInt = ((*array)[n])->asInt();
+ if (itemInt == nullptr) {
+ LOG(ERROR) << "An item in the accessControlProfileIds array is not a bstr";
+ return {};
+ }
+ accessControlProfileIds.push_back(itemInt->value());
+ }
+ return accessControlProfileIds;
+}
+
+optional<vector<vector<uint8_t>>> parseEncryptedChunks(const cppbor::Item& item) {
+ const cppbor::Array* array = item.asArray();
+ if (array == nullptr) {
+ LOG(ERROR) << "The encryptedChunks member is not an array";
+ return {};
+ }
+
+ vector<vector<uint8_t>> encryptedChunks;
+ for (size_t n = 0; n < array->size(); n++) {
+ const cppbor::Bstr* itemBstr = ((*array)[n])->asBstr();
+ if (itemBstr == nullptr) {
+ LOG(ERROR) << "An item in the encryptedChunks array is not a bstr";
+ return {};
+ }
+ encryptedChunks.push_back(itemBstr->value());
+ }
+ return encryptedChunks;
+}
+
+bool CredentialData::loadFromDisk() {
+
+ // Reset all data.
+ credentialData_.clear();
+ attestationCertificate_.clear();
+ secureAccessControlProfiles_.clear();
+ idToEncryptedChunks_.clear();
+ authKeyDatas_.clear();
+ keyCount_ = 0;
+ maxUsesPerKey_ = 1;
+
+ optional<vector<uint8_t>> data = fileGetContents(fileName_);
+ if (!data) {
+ LOG(ERROR) << "Error loading data";
+ return false;
+ }
+
+ auto [item, _ /* newPos */, message] = cppbor::parse(data.value());
+ if (item == nullptr) {
+ LOG(ERROR) << "Data loaded from " << fileName_ << " is not valid CBOR: " << message;
+ return false;
+ }
+
+ const cppbor::Map* map = item->asMap();
+ if (map == nullptr) {
+ LOG(ERROR) << "Top-level item is not a map";
+ return false;
+ }
+
+ for (size_t n = 0; n < map->size(); n++) {
+ auto [keyItem, valueItem] = (*map)[n];
+ const cppbor::Tstr* tstr = keyItem->asTstr();
+ if (tstr == nullptr) {
+ LOG(ERROR) << "Key item in top-level map is not a tstr";
+ return false;
+ }
+ const string& key = tstr->value();
+
+ if (key == "secureUserId") {
+ const cppbor::Int* number = valueItem->asInt();
+ if (number == nullptr) {
+ LOG(ERROR) << "Value for secureUserId is not a number";
+ return false;
+ }
+ secureUserId_ = number->value();
+ } else if (key == "credentialData") {
+ const cppbor::Bstr* valueBstr = valueItem->asBstr();
+ if (valueBstr == nullptr) {
+ LOG(ERROR) << "Value for credentialData is not a bstr";
+ return false;
+ }
+ credentialData_ = valueBstr->value();
+ } else if (key == "attestationCertificate") {
+ const cppbor::Bstr* valueBstr = valueItem->asBstr();
+ if (valueBstr == nullptr) {
+ LOG(ERROR) << "Value for attestationCertificate is not a bstr";
+ return false;
+ }
+ attestationCertificate_ = valueBstr->value();
+ } else if (key == "secureAccessControlProfiles") {
+ const cppbor::Array* array = valueItem->asArray();
+ if (array == nullptr) {
+ LOG(ERROR) << "Value for attestationCertificate is not an array";
+ return false;
+ }
+ for (size_t m = 0; m < array->size(); m++) {
+ const std::unique_ptr<cppbor::Item>& item = (*array)[m];
+ optional<SecureAccessControlProfile> sacp = parseSacp(*item);
+ if (!sacp) {
+ LOG(ERROR) << "Error parsing SecureAccessControlProfile";
+ return false;
+ }
+ secureAccessControlProfiles_.push_back(sacp.value());
+ }
+
+ } else if (key == "entryData") {
+ const cppbor::Map* map = valueItem->asMap();
+ if (map == nullptr) {
+ LOG(ERROR) << "Value for encryptedChunks is not an map";
+ return false;
+ }
+ for (size_t m = 0; m < map->size(); m++) {
+ auto [ecKeyItem, ecValueItem] = (*map)[m];
+ const cppbor::Tstr* ecTstr = ecKeyItem->asTstr();
+ if (ecTstr == nullptr) {
+ LOG(ERROR) << "Key item in encryptedChunks map is not a tstr";
+ return false;
+ }
+ const string& ecId = ecTstr->value();
+
+ const cppbor::Array* ecEntryArrayItem = ecValueItem->asArray();
+ if (ecEntryArrayItem == nullptr || ecEntryArrayItem->size() < 3) {
+ LOG(ERROR) << "Value item in encryptedChunks map is an array with at least two "
+ "elements";
+ return false;
+ }
+ const cppbor::Int* ecEntrySizeItem = (*ecEntryArrayItem)[0]->asInt();
+ if (ecEntrySizeItem == nullptr) {
+ LOG(ERROR) << "Entry size not a number";
+ return false;
+ }
+ uint64_t entrySize = ecEntrySizeItem->value();
+
+ optional<vector<uint16_t>> accessControlProfileIds =
+ parseAccessControlProfileIds(*(*ecEntryArrayItem)[1]);
+ if (!accessControlProfileIds) {
+ LOG(ERROR) << "Error parsing access control profile ids";
+ return false;
+ }
+
+ optional<vector<vector<uint8_t>>> encryptedChunks =
+ parseEncryptedChunks(*(*ecEntryArrayItem)[2]);
+ if (!encryptedChunks) {
+ LOG(ERROR) << "Error parsing encrypted chunks";
+ return false;
+ }
+
+ EntryData data;
+ data.size = entrySize;
+ data.accessControlProfileIds = accessControlProfileIds.value();
+ data.encryptedChunks = encryptedChunks.value();
+ idToEncryptedChunks_[ecId] = data;
+ }
+
+ } else if (key == "authKeyData") {
+ const cppbor::Array* array = valueItem->asArray();
+ if (array == nullptr) {
+ LOG(ERROR) << "Value for authData is not an array";
+ return false;
+ }
+ for (size_t m = 0; m < array->size(); m++) {
+ const std::unique_ptr<cppbor::Item>& item = (*array)[m];
+ optional<AuthKeyData> authKeyData = parseAuthKeyData(*item);
+ if (!authKeyData) {
+ LOG(ERROR) << "Error parsing AuthKeyData";
+ return false;
+ }
+ authKeyDatas_.push_back(authKeyData.value());
+ }
+
+ } else if (key == "authKeyCount") {
+ const cppbor::Int* number = valueItem->asInt();
+ if (number == nullptr) {
+ LOG(ERROR) << "Value for authKeyCount is not a number";
+ return false;
+ }
+ keyCount_ = number->value();
+
+ } else if (key == "maxUsesPerAuthKey") {
+ const cppbor::Int* number = valueItem->asInt();
+ if (number == nullptr) {
+ LOG(ERROR) << "Value for maxUsesPerAuthKey is not a number";
+ return false;
+ }
+ maxUsesPerKey_ = number->value();
+ }
+ }
+
+ if (credentialData_.size() == 0 || attestationCertificate_.size() == 0) {
+ LOG(ERROR) << "Missing credentialData or attestationCertificate";
+ return false;
+ }
+
+ if (size_t(keyCount_) != authKeyDatas_.size()) {
+ LOG(ERROR) << "keyCount_=" << keyCount_
+ << " != authKeyDatas_.size()=" << authKeyDatas_.size();
+ return false;
+ }
+
+ return true;
+}
+
+const vector<uint8_t>& CredentialData::getCredentialData() const {
+ return credentialData_;
+}
+
+int64_t CredentialData::getSecureUserId() {
+ return secureUserId_;
+}
+
+const vector<uint8_t>& CredentialData::getAttestationCertificate() const {
+ return attestationCertificate_;
+}
+
+const vector<SecureAccessControlProfile>& CredentialData::getSecureAccessControlProfiles() const {
+ return secureAccessControlProfiles_;
+}
+
+bool CredentialData::hasEntryData(const string& namespaceName, const string& entryName) const {
+ string id = namespaceName + ":" + entryName;
+ auto iter = idToEncryptedChunks_.find(id);
+ if (iter == idToEncryptedChunks_.end()) {
+ return false;
+ }
+ return true;
+}
+
+optional<EntryData> CredentialData::getEntryData(const string& namespaceName,
+ const string& entryName) const {
+ string id = namespaceName + ":" + entryName;
+ auto iter = idToEncryptedChunks_.find(id);
+ if (iter == idToEncryptedChunks_.end()) {
+ return {};
+ }
+ return iter->second;
+}
+
+bool CredentialData::deleteCredential() {
+ if (unlink(fileName_.c_str()) != 0) {
+ PLOG(ERROR) << "Error deleting " << fileName_;
+ return false;
+ }
+ return true;
+}
+
+optional<bool> CredentialData::credentialExists(const string& dataPath, uid_t ownerUid,
+ const string& name) {
+ struct stat statbuf;
+ string filename = calculateCredentialFileName(dataPath, ownerUid, name);
+ if (stat(filename.c_str(), &statbuf) != 0) {
+ if (errno == ENOENT) {
+ return false;
+ }
+ PLOG(ERROR) << "Error getting information about " << filename;
+ return {};
+ }
+ return true;
+}
+
+// ---
+
+void CredentialData::setAvailableAuthenticationKeys(int keyCount, int maxUsesPerKey) {
+ keyCount_ = keyCount;
+ maxUsesPerKey_ = maxUsesPerKey;
+
+ // If growing the number of auth keys (prevKeyCount < keyCount_ case) we'll add
+ // new AuthKeyData structs to |authKeyDatas_| and each struct will have empty |certificate|
+ // and |pendingCertificate| fields. Those will be filled out when the
+ // getAuthKeysNeedingCertification() is called.
+ //
+ // If shrinking, we'll just delete the AuthKeyData structs at the end. There's nothing
+ // else to do, the HAL doesn't need to know we're nuking these authentication keys.
+ //
+ // Therefore, in either case it's as simple as just resizing the vector.
+ authKeyDatas_.resize(keyCount_);
+}
+
+const vector<AuthKeyData>& CredentialData::getAuthKeyDatas() const {
+ return authKeyDatas_;
+}
+
+const AuthKeyData* CredentialData::selectAuthKey(bool allowUsingExhaustedKeys) {
+ AuthKeyData* candidate = nullptr;
+
+ int n = 0;
+ int candidateNum = -1;
+ for (AuthKeyData& data : authKeyDatas_) {
+ if (data.certificate.size() != 0) {
+ if (candidate == nullptr || data.useCount < candidate->useCount) {
+ candidate = &data;
+ candidateNum = n;
+ }
+ }
+ n++;
+ }
+
+ if (candidate == nullptr) {
+ return nullptr;
+ }
+
+ if (candidate->useCount >= maxUsesPerKey_ && !allowUsingExhaustedKeys) {
+ return nullptr;
+ }
+
+ candidate->useCount += 1;
+ return candidate;
+}
+
+optional<vector<vector<uint8_t>>> CredentialData::getAuthKeysNeedingCertification(
+ const sp<android::hardware::identity::V1_0::IIdentityCredential>& halBinder) {
+
+ vector<vector<uint8_t>> keysNeedingCert;
+
+ for (AuthKeyData& data : authKeyDatas_) {
+ bool newKeyNeeded = (data.certificate.size() == 0) || (data.useCount >= maxUsesPerKey_);
+ bool certificationPending = (data.pendingCertificate.size() > 0);
+ if (newKeyNeeded && !certificationPending) {
+ Result result;
+ vector<uint8_t> signingKeyBlob;
+ vector<uint8_t> signingKeyCertificate;
+ halBinder->generateSigningKeyPair(
+ [&](const Result& _result,
+ const android::hardware::hidl_vec<uint8_t> _signingKeyBlob,
+ const android::hardware::hidl_vec<uint8_t> _signingKeyCertificate) {
+ result = _result;
+ signingKeyBlob = _signingKeyBlob;
+ signingKeyCertificate = _signingKeyCertificate;
+ });
+ if (result.code != ResultCode::OK) {
+ LOG(ERROR) << "Error generating signing key-pair";
+ return {};
+ }
+ data.pendingCertificate = signingKeyCertificate;
+ data.pendingKeyBlob = signingKeyBlob;
+ certificationPending = true;
+ }
+
+ if (certificationPending) {
+ keysNeedingCert.push_back(data.pendingCertificate);
+ }
+ }
+ return keysNeedingCert;
+}
+
+bool CredentialData::storeStaticAuthenticationData(const vector<uint8_t>& authenticationKey,
+ const vector<uint8_t>& staticAuthData) {
+ for (AuthKeyData& data : authKeyDatas_) {
+ if (data.pendingCertificate == authenticationKey) {
+ data.certificate = data.pendingCertificate;
+ data.keyBlob = data.pendingKeyBlob;
+ data.staticAuthenticationData = staticAuthData;
+ data.pendingCertificate.clear();
+ data.pendingKeyBlob.clear();
+ data.useCount = 0;
+ return true;
+ }
+ }
+ return false;
+}
+
+} // namespace identity
+} // namespace security
+} // namespace android
diff --git a/identity/CredentialData.h b/identity/CredentialData.h
new file mode 100644
index 0000000..39e41f8
--- /dev/null
+++ b/identity/CredentialData.h
@@ -0,0 +1,144 @@
+/*
+ * 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.
+ */
+
+#ifndef SYSTEM_SECURITY_CREDENTIAL_DATA_H_
+#define SYSTEM_SECURITY_CREDENTIAL_DATA_H_
+
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <map>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <android/hardware/identity/1.0/IIdentityCredential.h>
+#include <android/hardware/identity/1.0/types.h>
+
+namespace android {
+namespace security {
+namespace identity {
+
+using ::android::hardware::identity::V1_0::SecureAccessControlProfile;
+using ::std::map;
+using ::std::optional;
+using ::std::pair;
+using ::std::string;
+using ::std::tuple;
+using ::std::vector;
+
+struct EntryData {
+ EntryData() {}
+
+ uint64_t size = 0;
+ vector<uint16_t> accessControlProfileIds;
+ vector<vector<uint8_t>> encryptedChunks;
+};
+
+struct AuthKeyData {
+ AuthKeyData() {}
+
+ vector<uint8_t> certificate;
+ vector<uint8_t> keyBlob;
+ vector<uint8_t> staticAuthenticationData;
+ vector<uint8_t> pendingCertificate;
+ vector<uint8_t> pendingKeyBlob;
+ int useCount = 0;
+};
+
+class CredentialData : public RefBase {
+ public:
+ CredentialData(const string& dataPath, uid_t ownerUid, const string& name);
+
+ static string calculateCredentialFileName(const string& dataPath, uid_t ownerUid,
+ const string& name);
+
+ static optional<bool> credentialExists(const string& dataPath, uid_t ownerUid,
+ const string& name);
+
+ void setSecureUserId(int64_t secureUserId);
+
+ void setCredentialData(const vector<uint8_t>& credentialData);
+
+ void setAttestationCertificate(const vector<uint8_t>& attestationCertificate);
+
+ void
+ addSecureAccessControlProfile(const SecureAccessControlProfile& secureAccessControlProfile);
+
+ void addEntryData(const string& namespaceName, const string& entryName, const EntryData& data);
+
+ bool saveToDisk() const;
+
+ bool loadFromDisk();
+
+ bool deleteCredential();
+
+ void setAvailableAuthenticationKeys(int keyCount, int maxUsesPerKey);
+
+ // Getters
+
+ int64_t getSecureUserId();
+
+ const vector<uint8_t>& getCredentialData() const;
+
+ const vector<uint8_t>& getAttestationCertificate() const;
+
+ const vector<SecureAccessControlProfile>& getSecureAccessControlProfiles() const;
+
+ bool hasEntryData(const string& namespaceName, const string& entryName) const;
+
+ optional<EntryData> getEntryData(const string& namespaceName, const string& entryName) const;
+
+ const vector<AuthKeyData>& getAuthKeyDatas() const;
+
+ // Returns |nullptr| if a suitable key cannot be found. Otherwise returns
+ // the authentication and increases its use-count.
+ const AuthKeyData* selectAuthKey(bool allowUsingExhaustedKeys);
+
+ optional<vector<vector<uint8_t>>> getAuthKeysNeedingCertification(
+ const sp<android::hardware::identity::V1_0::IIdentityCredential>& halBinder);
+
+ bool storeStaticAuthenticationData(const vector<uint8_t>& authenticationKey,
+ const vector<uint8_t>& staticAuthData);
+
+ private:
+ // Set by constructor.
+ //
+ string dataPath_;
+ uid_t ownerUid_;
+ string name_;
+
+ // Calculated at construction time, from |dataPath_|, |ownerUid_|, |name_|.
+ string fileName_;
+
+ // Data serialized in CBOR from here:
+ //
+ int64_t secureUserId_;
+ vector<uint8_t> credentialData_;
+ vector<uint8_t> attestationCertificate_;
+ vector<SecureAccessControlProfile> secureAccessControlProfiles_;
+ map<string, EntryData> idToEncryptedChunks_;
+
+ int keyCount_ = 0;
+ int maxUsesPerKey_ = 1;
+ vector<AuthKeyData> authKeyDatas_; // Always |keyCount_| long.
+};
+
+} // namespace identity
+} // namespace security
+} // namespace android
+
+#endif // SYSTEM_SECURITY_CREDENTIAL_DATA_H_
diff --git a/identity/CredentialStore.cpp b/identity/CredentialStore.cpp
new file mode 100644
index 0000000..b13a7b0
--- /dev/null
+++ b/identity/CredentialStore.cpp
@@ -0,0 +1,161 @@
+/*
+ * 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 "CredentialStore"
+
+#include <algorithm>
+
+#include <android-base/logging.h>
+
+#include <binder/IPCThreadState.h>
+
+#include "Credential.h"
+#include "CredentialStore.h"
+#include "Util.h"
+#include "WritableCredential.h"
+
+namespace android {
+namespace security {
+namespace identity {
+
+using ::android::hardware::hidl_string;
+using ::android::hardware::hidl_vec;
+using ::android::hardware::identity::V1_0::Result;
+using ::android::hardware::identity::V1_0::ResultCode;
+
+using ::android::hardware::identity::V1_0::IWritableIdentityCredential;
+
+CredentialStore::CredentialStore(const std::string& dataPath, sp<IIdentityCredentialStore> hal)
+ : dataPath_(dataPath), hal_(hal) {}
+
+bool CredentialStore::init() {
+ Result result;
+ hal_->getHardwareInformation([&](const Result& _result, const hidl_string& credentialStoreName,
+ const hidl_string& credentialStoreAuthorName,
+ uint32_t _dataChunkSize, bool _isDirectAccess,
+ const hidl_vec<hidl_string>& _supportedDocTypes) {
+ result = _result;
+ dataChunkSize_ = _dataChunkSize;
+ isDirectAccess_ = _isDirectAccess;
+ supportedDocTypes_.clear();
+ for (auto& docType : _supportedDocTypes) {
+ supportedDocTypes_.push_back(docType);
+ }
+ LOG(INFO) << "Connected to Identity Credential HAL with name '" << credentialStoreName
+ << "' authored by '" << credentialStoreAuthorName << "' with chunk size "
+ << _dataChunkSize << " and directoAccess set to "
+ << (_isDirectAccess ? "true" : "false");
+ });
+ if (result.code != ResultCode::OK) {
+ LOG(ERROR) << "Error getting hardware information: " << (int)result.code << ": "
+ << result.message;
+ return false;
+ }
+ return true;
+}
+
+CredentialStore::~CredentialStore() {}
+
+Status CredentialStore::getSecurityHardwareInfo(SecurityHardwareInfoParcel* _aidl_return) {
+ SecurityHardwareInfoParcel info;
+ info.directAccess = isDirectAccess_;
+ info.supportedDocTypes = supportedDocTypes_;
+ *_aidl_return = info;
+ return Status::ok();
+};
+
+Status CredentialStore::createCredential(const std::string& credentialName,
+ const std::string& docType,
+ sp<IWritableCredential>* _aidl_return) {
+ uid_t callingUid = android::IPCThreadState::self()->getCallingUid();
+ optional<bool> credentialExists =
+ CredentialData::credentialExists(dataPath_, callingUid, credentialName);
+ if (!credentialExists.has_value()) {
+ return Status::fromServiceSpecificError(
+ ERROR_GENERIC, "Error determining if credential with given name exists");
+ }
+ if (credentialExists.value()) {
+ return Status::fromServiceSpecificError(ERROR_ALREADY_PERSONALIZED,
+ "Credential with given name already exists");
+ }
+
+ if (supportedDocTypes_.size() > 0) {
+ if (std::find(supportedDocTypes_.begin(), supportedDocTypes_.end(), docType) ==
+ supportedDocTypes_.end()) {
+ return Status::fromServiceSpecificError(ERROR_DOCUMENT_TYPE_NOT_SUPPORTED,
+ "No support for given document type");
+ }
+ }
+
+ Result result;
+ sp<IWritableIdentityCredential> halWritableCredential;
+ hal_->createCredential(
+ docType, false,
+ [&](const Result& _result, const sp<IWritableIdentityCredential>& _halWritableCredential) {
+ result = _result;
+ halWritableCredential = _halWritableCredential;
+ });
+ if (result.code != ResultCode::OK) {
+ return halResultToGenericError(result);
+ }
+
+ sp<IWritableCredential> writableCredential = new WritableCredential(
+ dataPath_, credentialName, docType, dataChunkSize_, halWritableCredential);
+ *_aidl_return = writableCredential;
+ return Status::ok();
+}
+
+// Keep in sync with IdentityCredentialStore.java
+//
+
+const int CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256 = 1;
+
+Status CredentialStore::getCredentialByName(const std::string& credentialName, int32_t cipherSuite,
+ sp<ICredential>* _aidl_return) {
+ *_aidl_return = nullptr;
+
+ uid_t callingUid = android::IPCThreadState::self()->getCallingUid();
+ optional<bool> credentialExists =
+ CredentialData::credentialExists(dataPath_, callingUid, credentialName);
+ if (!credentialExists.has_value()) {
+ return Status::fromServiceSpecificError(
+ ERROR_GENERIC, "Error determining if credential with given name exists");
+ }
+ if (!credentialExists.value()) {
+ return Status::fromServiceSpecificError(ERROR_NO_SUCH_CREDENTIAL,
+ "Credential with given name doesn't exist");
+ }
+
+ // We only support a single cipher-suite right now.
+ if (cipherSuite != CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256) {
+ return Status::fromServiceSpecificError(ERROR_CIPHER_SUITE_NOT_SUPPORTED,
+ "Cipher suite not supported");
+ }
+
+ sp<Credential> credential = new Credential(dataPath_, credentialName);
+
+ Status loadStatus = credential->loadCredential(hal_);
+ if (!loadStatus.isOk()) {
+ LOG(ERROR) << "Error loading credential";
+ } else {
+ *_aidl_return = credential;
+ }
+ return loadStatus;
+}
+
+} // namespace identity
+} // namespace security
+} // namespace android
diff --git a/identity/CredentialStore.h b/identity/CredentialStore.h
new file mode 100644
index 0000000..8d679a2
--- /dev/null
+++ b/identity/CredentialStore.h
@@ -0,0 +1,70 @@
+/*
+ * 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.
+ */
+
+#ifndef SYSTEM_SECURITY_CREDENTIAL_STORE_H_
+#define SYSTEM_SECURITY_CREDENTIAL_STORE_H_
+
+#include <string>
+#include <vector>
+
+#include <android/hardware/identity/1.0/IIdentityCredentialStore.h>
+#include <android/hardware/identity/1.0/types.h>
+
+#include <android/security/identity/BnCredentialStore.h>
+
+namespace android {
+namespace security {
+namespace identity {
+
+using ::android::sp;
+using ::android::binder::Status;
+using ::std::string;
+using ::std::unique_ptr;
+using ::std::vector;
+
+using ::android::hardware::identity::V1_0::IIdentityCredentialStore;
+
+class CredentialStore : public BnCredentialStore {
+ public:
+ CredentialStore(const string& dataPath, sp<IIdentityCredentialStore> hal);
+ ~CredentialStore();
+
+ bool init();
+
+ // ICredentialStore overrides
+ Status getSecurityHardwareInfo(SecurityHardwareInfoParcel* _aidl_return) override;
+
+ Status createCredential(const string& credentialName, const string& docType,
+ sp<IWritableCredential>* _aidl_return) override;
+
+ Status getCredentialByName(const string& credentialName, int32_t cipherSuite,
+ sp<ICredential>* _aidl_return) override;
+
+ private:
+ string dataPath_;
+
+ sp<IIdentityCredentialStore> hal_;
+
+ bool isDirectAccess_;
+ vector<string> supportedDocTypes_;
+ size_t dataChunkSize_;
+};
+
+} // namespace identity
+} // namespace security
+} // namespace android
+
+#endif // SYSTEM_SECURITY_CREDENTIAL_STORE_H_
diff --git a/identity/CredentialStoreFactory.cpp b/identity/CredentialStoreFactory.cpp
new file mode 100644
index 0000000..947adf3
--- /dev/null
+++ b/identity/CredentialStoreFactory.cpp
@@ -0,0 +1,91 @@
+/*
+ * 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 "CredentialStoreFactory"
+
+#include <android-base/logging.h>
+
+#include <binder/IPCThreadState.h>
+
+#include "CredentialStore.h"
+#include "CredentialStoreFactory.h"
+
+namespace android {
+namespace security {
+namespace identity {
+
+using ::android::hardware::hidl_string;
+using ::android::hardware::hidl_vec;
+using ::android::hardware::identity::V1_0::IIdentityCredentialStore;
+using ::android::hardware::identity::V1_0::Result;
+using ::android::hardware::identity::V1_0::ResultCode;
+
+CredentialStoreFactory::CredentialStoreFactory(const std::string& dataPath) : dataPath_(dataPath) {}
+
+CredentialStoreFactory::~CredentialStoreFactory() {}
+
+CredentialStore* CredentialStoreFactory::createCredentialStore(const string& serviceName) {
+ sp<IIdentityCredentialStore> hal = IIdentityCredentialStore::tryGetService(serviceName);
+ if (hal.get() == nullptr) {
+ LOG(ERROR) << "Error get hal for store with service name '" << serviceName << "'";
+ return nullptr;
+ }
+
+ CredentialStore* store = new CredentialStore(dataPath_, hal);
+ if (!store->init()) {
+ LOG(ERROR) << "Error initializing CredentialStore with service name '" << serviceName
+ << "'";
+ delete store;
+ return nullptr;
+ }
+
+ return store;
+}
+
+Status CredentialStoreFactory::getCredentialStore(int32_t credentialStoreType,
+ sp<ICredentialStore>* _aidl_return) {
+ switch (credentialStoreType) {
+ case CREDENTIAL_STORE_TYPE_DEFAULT:
+ if (defaultStore_.get() == nullptr) {
+ defaultStore_ = createCredentialStore("default");
+ }
+ if (defaultStore_.get() == nullptr) {
+ return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC,
+ "Error creating default store");
+ }
+ *_aidl_return = defaultStore_.get();
+ return Status::ok();
+
+ case CREDENTIAL_STORE_TYPE_DIRECT_ACCESS:
+ if (directAccessStore_.get() == nullptr) {
+ directAccessStore_ = createCredentialStore("directAccess");
+ }
+ if (directAccessStore_.get() == nullptr) {
+ return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC,
+ "Error creating direct access store");
+ }
+ *_aidl_return = directAccessStore_.get();
+ return Status::ok();
+ break;
+ }
+
+ return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC,
+ "Unknown credential store type");
+}
+
+} // namespace identity
+} // namespace security
+} // namespace android
diff --git a/identity/CredentialStoreFactory.h b/identity/CredentialStoreFactory.h
new file mode 100644
index 0000000..3aef618
--- /dev/null
+++ b/identity/CredentialStoreFactory.h
@@ -0,0 +1,53 @@
+/*
+ * 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.
+ */
+
+#ifndef SYSTEM_SECURITY_CREDENTIAL_STORE_FACTORY_H_
+#define SYSTEM_SECURITY_CREDENTIAL_STORE_FACTORY_H_
+
+#include <android/security/identity/BnCredentialStoreFactory.h>
+
+#include "CredentialStore.h"
+
+namespace android {
+namespace security {
+namespace identity {
+
+using ::android::sp;
+using ::android::binder::Status;
+using ::std::string;
+
+class CredentialStoreFactory : public BnCredentialStoreFactory {
+ public:
+ explicit CredentialStoreFactory(const string& dataPath);
+ ~CredentialStoreFactory();
+
+ Status getCredentialStore(int32_t credentialStoreType,
+ sp<ICredentialStore>* _aidl_return) override;
+
+ private:
+ CredentialStore* createCredentialStore(const string& serviceName);
+
+ string dataPath_;
+
+ sp<CredentialStore> defaultStore_;
+ sp<CredentialStore> directAccessStore_;
+};
+
+} // namespace identity
+} // namespace security
+} // namespace android
+
+#endif // SYSTEM_SECURITY_CREDENTIAL_STORE_FACTORY_H_
diff --git a/identity/Util.cpp b/identity/Util.cpp
new file mode 100644
index 0000000..c397913
--- /dev/null
+++ b/identity/Util.cpp
@@ -0,0 +1,125 @@
+/*
+ * 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 "Util"
+
+#include <fcntl.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+
+#include <android/security/identity/ICredentialStore.h>
+
+#include "Util.h"
+
+namespace android {
+namespace security {
+namespace identity {
+
+using ::android::base::StringPrintf;
+
+Status halResultToGenericError(const Result& result) {
+ string message =
+ StringPrintf("HAL failed with code %d: %s", int(result.code), result.message.c_str());
+ return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC, message.c_str());
+}
+
+optional<vector<uint8_t>> fileGetContents(const string& path) {
+ int fd = open(path.c_str(), O_RDONLY);
+ if (fd == -1) {
+ PLOG(ERROR) << "Error opening " << path;
+ return {};
+ }
+
+ struct stat statbuf;
+ if (fstat(fd, &statbuf) != 0) {
+ PLOG(ERROR) << "Error statting " << path;
+ close(fd);
+ return {};
+ }
+ vector<uint8_t> data;
+ data.resize(statbuf.st_size);
+
+ uint8_t* p = data.data();
+ size_t remaining = data.size();
+ while (remaining > 0) {
+ size_t numRead = TEMP_FAILURE_RETRY(read(fd, p, remaining));
+ if (numRead <= 0) {
+ PLOG(ERROR) << "Failed reading from '" << path << "'";
+ close(fd);
+ return {};
+ }
+ p += numRead;
+ remaining -= numRead;
+ }
+ close(fd);
+
+ return data;
+}
+
+bool fileSetContents(const string& path, const vector<uint8_t>& data) {
+ char tempName[4096];
+ int fd;
+
+ string tempNameStr = path + ".XXXXXX";
+ if (tempNameStr.size() >= sizeof tempName - 1) {
+ LOG(ERROR) << "Path name too long";
+ return false;
+ }
+ strncpy(tempName, tempNameStr.c_str(), sizeof tempName);
+
+ fd = mkstemp(tempName);
+ if (fd == -1) {
+ PLOG(ERROR) << "Error creating temp file for '" << path << "'";
+ return false;
+ }
+
+ const uint8_t* p = data.data();
+ size_t remaining = data.size();
+ while (remaining > 0) {
+ size_t numWritten = TEMP_FAILURE_RETRY(write(fd, p, remaining));
+ if (numWritten <= 0) {
+ PLOG(ERROR) << "Failed writing into temp file for '" << path << "'";
+ close(fd);
+ return false;
+ }
+ p += numWritten;
+ remaining -= numWritten;
+ }
+
+ if (TEMP_FAILURE_RETRY(fsync(fd) == -1)) {
+ PLOG(ERROR) << "Failed fsyncing temp file for '" << path << "'";
+ close(fd);
+ return false;
+ }
+ close(fd);
+
+ if (rename(tempName, path.c_str()) != 0) {
+ PLOG(ERROR) << "Error renaming temp file for '" << path << "'";
+ close(fd);
+ return false;
+ }
+
+ return true;
+}
+
+} // namespace identity
+} // namespace security
+} // namespace android
diff --git a/identity/Util.h b/identity/Util.h
new file mode 100644
index 0000000..bb0e275
--- /dev/null
+++ b/identity/Util.h
@@ -0,0 +1,56 @@
+/*
+ * 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.
+ */
+
+#ifndef SYSTEM_SECURITY_IDENTITY_UTIL_H_
+#define SYSTEM_SECURITY_IDENTITY_UTIL_H_
+
+#include <string>
+#include <vector>
+
+#include <android/hardware/identity/1.0/IIdentityCredentialStore.h>
+#include <android/hardware/identity/1.0/types.h>
+#include <binder/Status.h>
+
+namespace android {
+namespace security {
+namespace identity {
+
+using ::std::optional;
+using ::std::string;
+using ::std::vector;
+
+using ::android::binder::Status;
+using ::android::hardware::identity::V1_0::Result;
+
+Status halResultToGenericError(const Result& result);
+
+// Helper function to atomically write |data| into file at |path|.
+//
+// Returns true on success, false on error.
+//
+bool fileSetContents(const string& path, const vector<uint8_t>& data);
+
+// Helper function which reads contents offile at |path| into |data|.
+//
+// Returns nothing on error, the content on success.
+//
+optional<vector<uint8_t>> fileGetContents(const string& path);
+
+} // namespace identity
+} // namespace security
+} // namespace android
+
+#endif // SYSTEM_SECURITY_IDENTITY_UTIL_H_
diff --git a/identity/WritableCredential.cpp b/identity/WritableCredential.cpp
new file mode 100644
index 0000000..f58ec16
--- /dev/null
+++ b/identity/WritableCredential.cpp
@@ -0,0 +1,192 @@
+/*
+ * 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 "CredentialData.h"
+#include "Util.h"
+#include "WritableCredential.h"
+
+namespace android {
+namespace security {
+namespace identity {
+
+using ::std::pair;
+
+using ::android::hardware::hidl_vec;
+
+using ::android::hardware::identity::V1_0::Result;
+using ::android::hardware::identity::V1_0::ResultCode;
+using ::android::hardware::identity::V1_0::SecureAccessControlProfile;
+
+using ::android::hardware::identity::support::chunkVector;
+
+WritableCredential::WritableCredential(const string& dataPath, const string& credentialName,
+ const string& /*docType*/, size_t dataChunkSize,
+ sp<IWritableIdentityCredential> halBinder)
+ : dataPath_(dataPath), credentialName_(credentialName), dataChunkSize_(dataChunkSize),
+ halBinder_(halBinder) {}
+
+WritableCredential::~WritableCredential() {}
+
+Status WritableCredential::ensureAttestationCertificateExists(const vector<uint8_t>& challenge) {
+ vector<uint8_t> attestationCertificate;
+
+ if (!attestationCertificate_.empty()) {
+ return Status::ok();
+ }
+
+ Result result;
+ halBinder_->getAttestationCertificate(
+ challenge, [&](const Result& _result, const hidl_vec<uint8_t>& _attestationCertificate) {
+ result = _result;
+ attestationCertificate = _attestationCertificate;
+ });
+ if (result.code != ResultCode::OK) {
+ LOG(ERROR) << "Error calling getAttestationCertificate()";
+ return halResultToGenericError(result);
+ }
+ attestationCertificate_ = attestationCertificate;
+ return Status::ok();
+}
+
+Status WritableCredential::getCredentialKeyCertificateChain(const vector<uint8_t>& challenge,
+ vector<uint8_t>* _aidl_return) {
+
+ Status ensureStatus = ensureAttestationCertificateExists(challenge);
+ if (!ensureStatus.isOk()) {
+ return ensureStatus;
+ }
+
+ *_aidl_return = attestationCertificate_;
+ return Status::ok();
+}
+
+Status
+WritableCredential::personalize(const vector<AccessControlProfileParcel>& accessControlProfiles,
+ const vector<EntryNamespaceParcel>& entryNamespaces,
+ int64_t secureUserId, vector<uint8_t>* _aidl_return) {
+ Status ensureStatus = ensureAttestationCertificateExists({});
+ 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<uint16_t> entryCounts;
+ for (const EntryNamespaceParcel& ensParcel : entryNamespaces) {
+ entryCounts.push_back(ensParcel.entries.size());
+ }
+
+ Result result;
+ halBinder_->startPersonalization(accessControlProfiles.size(), entryCounts,
+ [&](const Result& _result) { result = _result; });
+ if (result.code != ResultCode::OK) {
+ return halResultToGenericError(result);
+ }
+
+ for (const AccessControlProfileParcel& acpParcel : accessControlProfiles) {
+ halBinder_->addAccessControlProfile(
+ acpParcel.id, acpParcel.readerCertificate, acpParcel.userAuthenticationRequired,
+ acpParcel.userAuthenticationTimeoutMillis, secureUserId,
+ [&](const Result& _result, const SecureAccessControlProfile& profile) {
+ data.addSecureAccessControlProfile(profile);
+ result = _result;
+ });
+ if (result.code != ResultCode::OK) {
+ return halResultToGenericError(result);
+ }
+ }
+
+ for (const EntryNamespaceParcel& ensParcel : entryNamespaces) {
+ for (const EntryParcel& eParcel : ensParcel.entries) {
+ vector<vector<uint8_t>> chunks = chunkVector(eParcel.value, dataChunkSize_);
+
+ vector<uint16_t> ids;
+ std::copy(eParcel.accessControlProfileIds.begin(),
+ eParcel.accessControlProfileIds.end(), std::back_inserter(ids));
+
+ halBinder_->beginAddEntry(ids, ensParcel.namespaceName, eParcel.name,
+ eParcel.value.size(),
+ [&](const Result& _result) { result = _result; });
+ if (result.code != ResultCode::OK) {
+ return halResultToGenericError(result);
+ }
+
+ vector<vector<uint8_t>> encryptedChunks;
+ for (const auto& chunk : chunks) {
+ halBinder_->addEntryValue(
+ chunk, [&](const Result& _result, const hidl_vec<uint8_t>& encryptedContent) {
+ result = _result;
+ encryptedChunks.push_back(encryptedContent);
+ });
+ if (result.code != ResultCode::OK) {
+ return halResultToGenericError(result);
+ }
+ }
+ 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;
+ halBinder_->finishAddingEntries([&](const Result& _result,
+ const hidl_vec<uint8_t>& _credentialData,
+ const hidl_vec<uint8_t>& _proofOfProvisioningSignature) {
+ data.setCredentialData(_credentialData);
+ result = _result;
+ credentialData = _credentialData;
+ proofOfProvisioningSignature = _proofOfProvisioningSignature;
+ });
+ if (result.code != ResultCode::OK) {
+ return halResultToGenericError(result);
+ }
+
+ if (!data.saveToDisk()) {
+ return Status::fromServiceSpecificError(ICredentialStore::ERROR_GENERIC,
+ "Error saving credential data to disk");
+ }
+
+ *_aidl_return = proofOfProvisioningSignature;
+ return Status::ok();
+}
+
+} // namespace identity
+} // namespace security
+} // namespace android
diff --git a/identity/WritableCredential.h b/identity/WritableCredential.h
new file mode 100644
index 0000000..47987ce
--- /dev/null
+++ b/identity/WritableCredential.h
@@ -0,0 +1,65 @@
+/*
+ * 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.
+ */
+
+#ifndef SYSTEM_SECURITY_WRITABLE_CREDENTIAL_H_
+#define SYSTEM_SECURITY_WRITABLE_CREDENTIAL_H_
+
+#include <string>
+#include <vector>
+
+#include <android/security/identity/BnWritableCredential.h>
+
+#include <android/hardware/identity/1.0/IIdentityCredentialStore.h>
+#include <android/hardware/identity/1.0/types.h>
+
+namespace android {
+namespace security {
+namespace identity {
+
+using ::android::binder::Status;
+using ::android::hardware::identity::V1_0::IWritableIdentityCredential;
+using ::std::string;
+using ::std::vector;
+
+class WritableCredential : public BnWritableCredential {
+ public:
+ WritableCredential(const string& dataPath, const string& credentialName, const string& docType,
+ size_t dataChunkSize, sp<IWritableIdentityCredential> halBinder);
+ ~WritableCredential();
+
+ // IWritableCredential overrides
+ Status getCredentialKeyCertificateChain(const vector<uint8_t>& challenge,
+ vector<uint8_t>* _aidl_return) override;
+
+ Status personalize(const vector<AccessControlProfileParcel>& accessControlProfiles,
+ const vector<EntryNamespaceParcel>& entryNamespaces, int64_t secureUserId,
+ vector<uint8_t>* _aidl_return) override;
+
+ private:
+ string dataPath_;
+ string credentialName_;
+ size_t dataChunkSize_;
+ sp<IWritableIdentityCredential> halBinder_;
+ vector<uint8_t> attestationCertificate_;
+
+ Status ensureAttestationCertificateExists(const vector<uint8_t>& challenge);
+};
+
+} // namespace identity
+} // namespace security
+} // namespace android
+
+#endif // SYSTEM_SECURITY_WRITABLE_CREDENTIAL_H_
diff --git a/identity/binder/android/security/identity/AccessControlProfileParcel.aidl b/identity/binder/android/security/identity/AccessControlProfileParcel.aidl
new file mode 100644
index 0000000..4bb85a9
--- /dev/null
+++ b/identity/binder/android/security/identity/AccessControlProfileParcel.aidl
@@ -0,0 +1,27 @@
+/*
+ * 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.
+ */
+
+package android.security.identity;
+
+/**
+ * @hide
+ */
+parcelable AccessControlProfileParcel {
+ int id;
+ byte[] readerCertificate;
+ boolean userAuthenticationRequired;
+ long userAuthenticationTimeoutMillis;
+}
diff --git a/identity/binder/android/security/identity/AuthKeyParcel.aidl b/identity/binder/android/security/identity/AuthKeyParcel.aidl
new file mode 100644
index 0000000..9374b8e
--- /dev/null
+++ b/identity/binder/android/security/identity/AuthKeyParcel.aidl
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+
+package android.security.identity;
+
+/**
+ * @hide
+ */
+parcelable AuthKeyParcel {
+ byte[] x509cert;
+}
diff --git a/identity/binder/android/security/identity/EntryNamespaceParcel.aidl b/identity/binder/android/security/identity/EntryNamespaceParcel.aidl
new file mode 100644
index 0000000..50758c8
--- /dev/null
+++ b/identity/binder/android/security/identity/EntryNamespaceParcel.aidl
@@ -0,0 +1,27 @@
+/*
+ * 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.
+ */
+
+package android.security.identity;
+
+import android.security.identity.EntryParcel;
+
+/**
+ * @hide
+ */
+parcelable EntryNamespaceParcel {
+ @utf8InCpp String namespaceName;
+ EntryParcel[] entries;
+}
diff --git a/identity/binder/android/security/identity/EntryParcel.aidl b/identity/binder/android/security/identity/EntryParcel.aidl
new file mode 100644
index 0000000..8d236d4
--- /dev/null
+++ b/identity/binder/android/security/identity/EntryParcel.aidl
@@ -0,0 +1,26 @@
+/*
+ * 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.
+ */
+
+package android.security.identity;
+
+/**
+ * @hide
+ */
+parcelable EntryParcel {
+ @utf8InCpp String name;
+ byte[] value;
+ int[] accessControlProfileIds;
+}
diff --git a/identity/binder/android/security/identity/GetEntriesResultParcel.aidl b/identity/binder/android/security/identity/GetEntriesResultParcel.aidl
new file mode 100644
index 0000000..03b363c
--- /dev/null
+++ b/identity/binder/android/security/identity/GetEntriesResultParcel.aidl
@@ -0,0 +1,29 @@
+/*
+ * 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.
+ */
+
+package android.security.identity;
+
+import android.security.identity.ResultNamespaceParcel;
+
+/**
+ * @hide
+ */
+parcelable GetEntriesResultParcel {
+ ResultNamespaceParcel[] resultNamespaces;
+ byte[] deviceNameSpaces;
+ byte[] mac;
+ byte[] staticAuthenticationData;
+}
diff --git a/identity/binder/android/security/identity/ICredential.aidl b/identity/binder/android/security/identity/ICredential.aidl
new file mode 100644
index 0000000..7bd0df7
--- /dev/null
+++ b/identity/binder/android/security/identity/ICredential.aidl
@@ -0,0 +1,62 @@
+/*
+ * 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.
+ */
+
+package android.security.identity;
+
+import android.security.identity.RequestNamespaceParcel;
+import android.security.identity.GetEntriesResultParcel;
+import android.security.identity.AuthKeyParcel;
+
+/**
+ * @hide
+ */
+interface ICredential {
+ /* The STATUS_* constants are used in the status field in ResultEntryParcel.
+ * Keep in sync with ResultNamespace.java.
+ */
+ const int STATUS_OK = 0;
+ const int STATUS_NO_SUCH_ENTRY = 1;
+ const int STATUS_NOT_REQUESTED = 2;
+ const int STATUS_NOT_IN_REQUEST_MESSAGE = 3;
+ const int STATUS_USER_AUTHENTICATION_FAILED = 4;
+ const int STATUS_READER_AUTHENTICATION_FAILED = 5;
+ const int STATUS_NO_ACCESS_CONTROL_PROFILES = 6;
+
+ byte[] createEphemeralKeyPair();
+
+ void setReaderEphemeralPublicKey(in byte[] publicKey);
+
+ byte[] deleteCredential();
+
+ byte[] getCredentialKeyCertificateChain();
+
+ long selectAuthKey(in boolean allowUsingExhaustedKeys);
+
+ GetEntriesResultParcel getEntries(in byte[] requestMessage,
+ in RequestNamespaceParcel[] requestNamespaces,
+ in byte[] sessionTranscript,
+ in byte[] readerSignature,
+ in boolean allowUsingExhaustedKeys);
+
+ void setAvailableAuthenticationKeys(in int keyCount, in int maxUsesPerKey);
+
+ AuthKeyParcel[] getAuthKeysNeedingCertification();
+
+ void storeStaticAuthenticationData(in AuthKeyParcel authenticationKey, in byte[] staticAuthData);
+
+ int[] getAuthenticationDataUsageCount();
+}
+
diff --git a/identity/binder/android/security/identity/ICredentialStore.aidl b/identity/binder/android/security/identity/ICredentialStore.aidl
new file mode 100644
index 0000000..1039831
--- /dev/null
+++ b/identity/binder/android/security/identity/ICredentialStore.aidl
@@ -0,0 +1,49 @@
+/*
+ * 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.
+ */
+
+package android.security.identity;
+
+import android.security.identity.IWritableCredential;
+import android.security.identity.ICredential;
+import android.security.identity.SecurityHardwareInfoParcel;
+
+/**
+ * @hide
+ */
+interface ICredentialStore {
+ /* All binder calls may return a ServiceSpecificException
+ * with the following error codes:
+ */
+ const int ERROR_NONE = 0;
+ const int ERROR_GENERIC = 1;
+ const int ERROR_ALREADY_PERSONALIZED = 2;
+ const int ERROR_NO_SUCH_CREDENTIAL = 3;
+ const int ERROR_CIPHER_SUITE_NOT_SUPPORTED = 4;
+ const int ERROR_EPHEMERAL_PUBLIC_KEY_NOT_FOUND = 5;
+ const int ERROR_NO_AUTHENTICATION_KEY_AVAILABLE = 6;
+ const int ERROR_INVALID_READER_SIGNATURE = 7;
+ const int ERROR_DOCUMENT_TYPE_NOT_SUPPORTED = 8;
+ const int ERROR_AUTHENTICATION_KEY_NOT_FOUND = 9;
+ const int ERROR_INVALID_ITEMS_REQUEST_MESSAGE = 10;
+ const int ERROR_SESSION_TRANSCRIPT_MISMATCH = 11;
+
+ SecurityHardwareInfoParcel getSecurityHardwareInfo();
+
+ IWritableCredential createCredential(in @utf8InCpp String credentialName,
+ in @utf8InCpp String docType);
+ ICredential getCredentialByName(in @utf8InCpp String credentialName,
+ in int cipherSuite);
+}
diff --git a/identity/binder/android/security/identity/ICredentialStoreFactory.aidl b/identity/binder/android/security/identity/ICredentialStoreFactory.aidl
new file mode 100644
index 0000000..3164f28
--- /dev/null
+++ b/identity/binder/android/security/identity/ICredentialStoreFactory.aidl
@@ -0,0 +1,29 @@
+/*
+ * 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.
+ */
+
+package android.security.identity;
+
+import android.security.identity.ICredentialStore;
+
+/**
+ * @hide
+ */
+interface ICredentialStoreFactory {
+ const int CREDENTIAL_STORE_TYPE_DEFAULT = 0;
+ const int CREDENTIAL_STORE_TYPE_DIRECT_ACCESS = 1;
+
+ ICredentialStore getCredentialStore(int credentialStoreType);
+}
diff --git a/identity/binder/android/security/identity/IWritableCredential.aidl b/identity/binder/android/security/identity/IWritableCredential.aidl
new file mode 100644
index 0000000..0e7ca4c
--- /dev/null
+++ b/identity/binder/android/security/identity/IWritableCredential.aidl
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ */
+
+package android.security.identity;
+
+import android.security.identity.AccessControlProfileParcel;
+import android.security.identity.EntryNamespaceParcel;
+
+/**
+ * @hide
+ */
+interface IWritableCredential {
+ byte[] getCredentialKeyCertificateChain(in byte[] challenge);
+ byte[] personalize(in AccessControlProfileParcel[] accessControlProfiles,
+ in EntryNamespaceParcel[] entryNamespaces,
+ in long secureUserId);
+}
diff --git a/identity/binder/android/security/identity/RequestEntryParcel.aidl b/identity/binder/android/security/identity/RequestEntryParcel.aidl
new file mode 100644
index 0000000..77d80c1
--- /dev/null
+++ b/identity/binder/android/security/identity/RequestEntryParcel.aidl
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+
+package android.security.identity;
+
+/**
+ * @hide
+ */
+parcelable RequestEntryParcel {
+ @utf8InCpp String name;
+}
diff --git a/identity/binder/android/security/identity/RequestNamespaceParcel.aidl b/identity/binder/android/security/identity/RequestNamespaceParcel.aidl
new file mode 100644
index 0000000..a4fb624
--- /dev/null
+++ b/identity/binder/android/security/identity/RequestNamespaceParcel.aidl
@@ -0,0 +1,27 @@
+/*
+ * 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.
+ */
+
+package android.security.identity;
+
+import android.security.identity.RequestEntryParcel;
+
+/**
+ * @hide
+ */
+parcelable RequestNamespaceParcel {
+ @utf8InCpp String namespaceName;
+ RequestEntryParcel[] entries;
+}
diff --git a/identity/binder/android/security/identity/ResultEntryParcel.aidl b/identity/binder/android/security/identity/ResultEntryParcel.aidl
new file mode 100644
index 0000000..fb4f9ef
--- /dev/null
+++ b/identity/binder/android/security/identity/ResultEntryParcel.aidl
@@ -0,0 +1,26 @@
+/*
+ * 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.
+ */
+
+package android.security.identity;
+
+/**
+ * @hide
+ */
+parcelable ResultEntryParcel {
+ int status;
+ @utf8InCpp String name;
+ byte[] value;
+}
diff --git a/identity/binder/android/security/identity/ResultNamespaceParcel.aidl b/identity/binder/android/security/identity/ResultNamespaceParcel.aidl
new file mode 100644
index 0000000..c9543d9
--- /dev/null
+++ b/identity/binder/android/security/identity/ResultNamespaceParcel.aidl
@@ -0,0 +1,27 @@
+/*
+ * 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.
+ */
+
+package android.security.identity;
+
+import android.security.identity.ResultEntryParcel;
+
+/**
+ * @hide
+ */
+parcelable ResultNamespaceParcel {
+ @utf8InCpp String namespaceName;
+ ResultEntryParcel[] entries;
+}
diff --git a/identity/binder/android/security/identity/SecurityHardwareInfoParcel.aidl b/identity/binder/android/security/identity/SecurityHardwareInfoParcel.aidl
new file mode 100644
index 0000000..cabaf21
--- /dev/null
+++ b/identity/binder/android/security/identity/SecurityHardwareInfoParcel.aidl
@@ -0,0 +1,25 @@
+/*
+ * 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.
+ */
+
+package android.security.identity;
+
+/**
+ * @hide
+ */
+parcelable SecurityHardwareInfoParcel {
+ boolean directAccess;
+ @utf8InCpp String[] supportedDocTypes;
+}
diff --git a/identity/credstore.rc b/identity/credstore.rc
new file mode 100644
index 0000000..d9e989a
--- /dev/null
+++ b/identity/credstore.rc
@@ -0,0 +1,4 @@
+service credstore /system/bin/credstore /data/misc/credstore
+ class core
+ user credstore
+ group credstore
diff --git a/identity/main.cpp b/identity/main.cpp
new file mode 100644
index 0000000..af03a30
--- /dev/null
+++ b/identity/main.cpp
@@ -0,0 +1,59 @@
+/*
+ * 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 "android.security.identity"
+
+#include <filesystem>
+
+#include <unistd.h>
+
+#include <android-base/logging.h>
+#include <binder/IPCThreadState.h>
+#include <binder/IServiceManager.h>
+
+#include "CredentialStoreFactory.h"
+
+#include <cppbor.h>
+
+using ::std::string;
+
+using ::android::IPCThreadState;
+using ::android::IServiceManager;
+using ::android::sp;
+using ::android::String16;
+using ::android::base::InitLogging;
+using ::android::base::StderrLogger;
+
+using ::android::security::identity::CredentialStoreFactory;
+
+int main(int argc, char* argv[]) {
+ InitLogging(argv, StderrLogger);
+
+ CHECK(argc == 2) << "A directory must be specified";
+ string data_dir = string(argv[1]);
+ CHECK(chdir(data_dir.c_str()) != -1) << "chdir: " << data_dir << ": " << strerror(errno);
+
+ sp<IServiceManager> sm = ::android::defaultServiceManager();
+ sp<CredentialStoreFactory> factory = new CredentialStoreFactory(data_dir);
+
+ auto ret = sm->addService(String16("android.security.identity"), factory);
+ CHECK(ret == ::android::OK) << "Couldn't register binder service";
+ LOG(ERROR) << "Registered binder service";
+
+ IPCThreadState::self()->joinThreadPool();
+
+ return 0;
+}
diff --git a/keystore/auth_token_table.cpp b/keystore/auth_token_table.cpp
index 6bffa7c..5e6d572 100644
--- a/keystore/auth_token_table.cpp
+++ b/keystore/auth_token_table.cpp
@@ -173,6 +173,60 @@
return {OK, newest_match->token()};
}
+std::tuple<AuthTokenTable::Error, HardwareAuthToken>
+AuthTokenTable::FindAuthorizationForCredstore(uint64_t challenge, uint64_t secureUserId,
+ int64_t authTokenMaxAgeMillis) {
+ std::vector<uint64_t> sids = {secureUserId};
+ HardwareAuthenticatorType auth_type = HardwareAuthenticatorType::ANY;
+
+ time_t now = clock_function_();
+
+ // challenge-based - the authToken has to contain the given challenge.
+ if (challenge != 0) {
+ auto matching_op = find_if(
+ entries_, [&](Entry& e) { return e.token().challenge == challenge && !e.completed(); });
+ if (matching_op == entries_.end()) {
+ return {AUTH_TOKEN_NOT_FOUND, {}};
+ }
+
+ if (!matching_op->SatisfiesAuth(sids, auth_type)) {
+ return {AUTH_TOKEN_WRONG_SID, {}};
+ }
+
+ if (authTokenMaxAgeMillis > 0) {
+ if (static_cast<int64_t>(matching_op->time_received()) + authTokenMaxAgeMillis <
+ static_cast<int64_t>(now)) {
+ return {AUTH_TOKEN_EXPIRED, {}};
+ }
+ }
+
+ return {OK, matching_op->token()};
+ }
+
+ // Otherwise, no challenge - any authToken younger than the specified maximum
+ // age will do.
+ Entry* newest_match = nullptr;
+ for (auto& entry : entries_) {
+ if (entry.SatisfiesAuth(sids, auth_type) && entry.is_newer_than(newest_match)) {
+ newest_match = &entry;
+ }
+ }
+
+ if (newest_match == nullptr) {
+ return {AUTH_TOKEN_NOT_FOUND, {}};
+ }
+
+ if (authTokenMaxAgeMillis > 0) {
+ if (static_cast<int64_t>(newest_match->time_received()) + authTokenMaxAgeMillis <
+ static_cast<int64_t>(now)) {
+ return {AUTH_TOKEN_EXPIRED, {}};
+ }
+ }
+
+ newest_match->UpdateLastUse(now);
+ return {OK, newest_match->token()};
+}
+
void AuthTokenTable::ExtractSids(const AuthorizationSet& key_info, std::vector<uint64_t>* sids) {
assert(sids);
for (auto& param : key_info)
diff --git a/keystore/auth_token_table.h b/keystore/auth_token_table.h
index 7b48a6c..86d65de 100644
--- a/keystore/auth_token_table.h
+++ b/keystore/auth_token_table.h
@@ -76,6 +76,10 @@
std::tuple<Error, HardwareAuthToken> FindAuthorization(const AuthorizationSet& key_info,
KeyPurpose purpose, uint64_t op_handle);
+ std::tuple<Error, HardwareAuthToken>
+ FindAuthorizationForCredstore(uint64_t challenge, uint64_t secureUserId,
+ int64_t authTokenMaxAgeMillis);
+
/**
* Mark operation completed. This allows tokens associated with the specified operation to be
* superseded by new tokens.
diff --git a/keystore/binder/android/security/keystore/IKeystoreService.aidl b/keystore/binder/android/security/keystore/IKeystoreService.aidl
index f230043..fcea115 100644
--- a/keystore/binder/android/security/keystore/IKeystoreService.aidl
+++ b/keystore/binder/android/security/keystore/IKeystoreService.aidl
@@ -84,4 +84,7 @@
boolean isConfirmationPromptSupported();
int onKeyguardVisibilityChanged(in boolean isShowing, in int userId);
int listUidsOfAuthBoundKeys(out @utf8InCpp List<String> uids);
+
+ // Called by credstore (and only credstore).
+ byte[] getAuthTokenForCredstore(in long challenge, in long secureUserId, in int authTokenMaxAgeMillis);
}
diff --git a/keystore/key_store_service.cpp b/keystore/key_store_service.cpp
index 5adc199..41b4109 100644
--- a/keystore/key_store_service.cpp
+++ b/keystore/key_store_service.cpp
@@ -38,6 +38,7 @@
#include <android/hardware/confirmationui/1.0/IConfirmationUI.h>
#include <android/hardware/keymaster/3.0/IHwKeymasterDevice.h>
+#include <keymasterV4_0/keymaster_utils.h>
#include "defaults.h"
#include "key_proto_handler.h"
@@ -612,8 +613,6 @@
const ::android::sp<::android::security::keystore::IKeystoreKeyCharacteristicsCallback>& cb,
const String16& name, const KeymasterArguments& params, const ::std::vector<uint8_t>& entropy,
int uid, int flags, int32_t* _aidl_return) {
- // TODO(jbires): remove this getCallingUid call upon implementation of b/25646100
- uid_t originalUid = IPCThreadState::self()->getCallingUid();
uid = getEffectiveUid(uid);
auto logOnScopeExit = android::base::make_scope_guard([&] {
if (__android_log_security()) {
@@ -633,9 +632,7 @@
}
if (containsTag(params.getParameters(), Tag::INCLUDE_UNIQUE_ID)) {
- // TODO(jbires): remove uid checking upon implementation of b/25646100
- if (!checkBinderPermission(P_GEN_UNIQUE_ID) ||
- originalUid != IPCThreadState::self()->getCallingUid()) {
+ if (!checkBinderPermission(P_GEN_UNIQUE_ID)) {
return AIDL_RETURN(ResponseCode::PERMISSION_DENIED);
}
}
@@ -946,6 +943,24 @@
return Status::ok();
}
+Status KeyStoreService::getAuthTokenForCredstore(int64_t challenge, int64_t secureUserId,
+ int32_t authTokenMaxAgeMillis,
+ std::vector<uint8_t>* _aidl_return) {
+ uid_t callingUid = IPCThreadState::self()->getCallingUid();
+ if (callingUid != AID_CREDSTORE) {
+ return Status::fromServiceSpecificError(static_cast<int32_t>(0));
+ }
+
+ auto [err, authToken] = mKeyStore->getAuthTokenTable().FindAuthorizationForCredstore(
+ challenge, secureUserId, authTokenMaxAgeMillis);
+ std::vector<uint8_t> ret;
+ if (err == AuthTokenTable::OK) {
+ ret = android::hardware::keymaster::V4_0::support::authToken2HidlVec(authToken);
+ }
+ *_aidl_return = ret;
+ return Status::ok();
+}
+
bool isDeviceIdAttestationRequested(const KeymasterArguments& params) {
const hardware::hidl_vec<KeyParameter>& paramsVec = params.getParameters();
for (size_t i = 0; i < paramsVec.size(); ++i) {
diff --git a/keystore/key_store_service.h b/keystore/key_store_service.h
index a395e6c..8c1d508 100644
--- a/keystore/key_store_service.h
+++ b/keystore/key_store_service.h
@@ -132,6 +132,9 @@
const ::android::sp<::android::IBinder>& token, int32_t* _aidl_return) override;
::android::binder::Status addAuthToken(const ::std::vector<uint8_t>& authToken,
int32_t* _aidl_return) override;
+ ::android::binder::Status
+ getAuthTokenForCredstore(int64_t challenge, int64_t secureUserId, int32_t authTokenMaxAge,
+ ::std::vector<uint8_t>* _aidl_return) override;
::android::binder::Status onUserAdded(int32_t userId, int32_t parentId,
int32_t* _aidl_return) override;
::android::binder::Status onUserRemoved(int32_t userId, int32_t* _aidl_return) override;