Multithreaded Keystore
This patch transitions keystore a threading model with one dispatcher
thread and one worker thread per keymaster instance, i.e. fallback, TEE,
Strongbox (if available). Singleton objects, such as the user state
database, the enforcement policy, and grant database have been moved to
KeyStore and were made concurrency safe.
Other noteworthy changes in this patch:
* Cached key characteristics. The key characteristics file used to hold
a limited set of parameters used generate or import the key. This
patch introduces a new blob type that holds full characteristics as
returned by generate, import, or getKeyCharacteristics, with the
original parameters mixed into the software enforced list. When
keystore encounters a lagacy characteristics file it will grab the
characteristics from keymaster, merge them with the cached parameters,
and update the cache file to the new format. If keystore encounters
the new cache no call to keymaster will be made for retrieving the
key characteristics.
* Changed semantic of list. The list call takes a prefix used for
filtering key entries. By the old semantic, list would return a list
of aliases stripped of the given prefix. By the new semantic list
always returns a filtered list of full alias string. Callers may
strip prefixes if they are so inclined.
* Entertain per keymaster instance operation maps. With the introduction
of Strongbox keystore had to deal with multiple keymaster instances.
But until now it would entertain a single operations map. Keystore
also enforces the invariant that no more than 15 operation slots are
used so there is always a free slot available for vold. With a single
operation map, this means no more than 15 slots can ever be used
although with TEE and Strongbox there are a total of 32 slots. With
strongbox implementation that have significantly fewer slots we see
another effect of the single operation map. If a slot needs to be
freed on Stronbox but the oldest operations are on TEE, the latter
will be unnecessarily pruned before a Strongbox slot is freed up.
With this patch each keymaster instance has its own operation map and
pruning is performed on a per keymaster instance basis.
* Introduce KeyBlobEntries which are independent from files. To allow
concurrent access to the key blob data base, entries can be
individually locked so that operations on entries become atomic.
LockedKeyBlobEntries are move only objects that track ownership of an
Entry on the stack or in functor object representing keymaster worker
requests. Entries must only be locked by the dispatcher Thread. Worker
threads can only be granted access to a LockedKeyBlobEntry by the
dispatcher thread. This allows the dispatcher thread to execute a
barrier that waits until all locks held by workers have been
relinquished to perform blob database maintenance operations, e.g.,
clearing a uid of all entries.
* Verification tokens are now acquired asynchronously. When a begin
operation requires a verification token a request is submitted to the
other keymaster worker while the begin call returns. When the
operation commences with update or finish, we block until the
verification token becomes available.
As of this patch the keystore IPC interface is still synchronous. That
is, the dispatcher thread dispatches a request to a worker and then
waits until the worker has finished. In a followup patch the IPC
interface shall be made asynchronous so that multiple requests may be in
flight.
Test: Ran full CTS test suite
atest android.keystore.cts
Bug: 111443219
Bug: 110495056
Change-Id: I305e28d784295a0095a34810d83202f7423498bd
diff --git a/keystore/KeyStore.h b/keystore/KeyStore.h
index 23476d2..d2049a4 100644
--- a/keystore/KeyStore.h
+++ b/keystore/KeyStore.h
@@ -23,41 +23,67 @@
#include <keystore/keymaster_types.h>
+#include "auth_token_table.h"
#include "blob.h"
+#include "confirmation_manager.h"
#include "grant_store.h"
+#include "keymaster_worker.h"
+#include "keystore_keymaster_enforcement.h"
+#include "operation.h"
#include "user_state.h"
+#include <array>
+#include <optional>
+#include <tuple>
+
namespace keystore {
using ::android::sp;
using keymaster::support::Keymaster;
-class KeymasterDevices : public std::array<sp<Keymaster>, 3> {
+template <typename T, size_t count> class Devices : public std::array<T, count> {
public:
- sp<Keymaster>& operator[](SecurityLevel secLevel);
- sp<Keymaster> operator[](SecurityLevel secLevel) const;
+ T& operator[](SecurityLevel secLevel) {
+ static_assert(uint32_t(SecurityLevel::SOFTWARE) == 0 &&
+ uint32_t(SecurityLevel::TRUSTED_ENVIRONMENT) == 1 &&
+ uint32_t(SecurityLevel::STRONGBOX) == 2,
+ "Numeric values of security levels have changed");
+ return std::array<T, count>::at(static_cast<uint32_t>(secLevel));
+ }
+ T operator[](SecurityLevel secLevel) const {
+ if (static_cast<uint32_t>(secLevel) > static_cast<uint32_t>(SecurityLevel::STRONGBOX)) {
+ LOG(ERROR) << "Invalid security level requested";
+ return {};
+ }
+ return (*const_cast<Devices*>(this))[secLevel];
+ }
};
-class KeyStore {
+} // namespace keystore
+
+namespace std {
+template <typename T, size_t count> class tuple_size<keystore::Devices<T, count>> {
+ public:
+ static constexpr size_t value = std::tuple_size<std::array<T, count>>::value;
+};
+} // namespace std
+
+namespace keystore {
+
+using KeymasterWorkers = Devices<std::shared_ptr<KeymasterWorker>, 3>;
+using KeymasterDevices = Devices<sp<Keymaster>, 3>;
+
+class KeyStore : public ::android::IBinder::DeathRecipient {
public:
KeyStore(Entropy* entropy, const KeymasterDevices& kmDevices,
SecurityLevel minimalAllowedSecurityLevelForNewKeys);
~KeyStore();
- sp<Keymaster> getDevice(SecurityLevel securityLevel) const { return mKmDevices[securityLevel]; }
-
- std::pair<sp<Keymaster>, SecurityLevel> getMostSecureDevice() const {
- SecurityLevel level = SecurityLevel::STRONGBOX;
- do {
- if (mKmDevices[level].get()) {
- return {mKmDevices[level], level};
- }
- level = static_cast<SecurityLevel>(static_cast<uint32_t>(level) - 1);
- } while (level != SecurityLevel::SOFTWARE);
- return {nullptr, SecurityLevel::SOFTWARE};
+ std::shared_ptr<KeymasterWorker> getDevice(SecurityLevel securityLevel) const {
+ return mKmDevices[securityLevel];
}
- sp<Keymaster> getFallbackDevice() const {
+ std::shared_ptr<KeymasterWorker> getFallbackDevice() const {
// we only return the fallback device if the creation of new fallback key blobs is
// allowed. (also see getDevice below)
if (mAllowNewFallback) {
@@ -67,11 +93,13 @@
}
}
- sp<Keymaster> getDevice(const Blob& blob) { return mKmDevices[blob.getSecurityLevel()]; }
+ std::shared_ptr<KeymasterWorker> getDevice(const Blob& blob) {
+ return mKmDevices[blob.getSecurityLevel()];
+ }
ResponseCode initialize();
- State getState(uid_t userId) { return getUserState(userId)->getState(); }
+ State getState(uid_t userId) { return mUserStateDB.getUserState(userId)->getState(); }
ResponseCode initializeUser(const android::String8& pw, uid_t userId);
@@ -79,14 +107,9 @@
ResponseCode writeMasterKey(const android::String8& pw, uid_t userId);
ResponseCode readMasterKey(const android::String8& pw, uid_t userId);
- android::String8 getKeyName(const android::String8& keyName, const BlobType type);
- android::String8 getKeyNameForUid(const android::String8& keyName, uid_t uid,
- const BlobType type);
- android::String8 getKeyNameForUidWithDir(const android::String8& keyName, uid_t uid,
- const BlobType type);
- NullOr<android::String8> getBlobFileNameIfExists(const android::String8& alias, uid_t uid,
- const BlobType type);
-
+ LockedKeyBlobEntry getLockedBlobEntryIfNotExists(const std::string& alias, uid_t uid);
+ std::optional<KeyBlobEntry> getBlobEntryIfExists(const std::string& alias, uid_t uid);
+ LockedKeyBlobEntry getLockedBlobEntryIfExists(const std::string& alias, uid_t uid);
/*
* Delete entries owned by userId. If keepUnencryptedEntries is true
* then only encrypted entries will be removed, otherwise all entries will
@@ -97,43 +120,28 @@
void lock(uid_t userId);
- ResponseCode get(const char* filename, Blob* keyBlob, const BlobType type, uid_t userId);
- ResponseCode put(const char* filename, Blob* keyBlob, uid_t userId);
- ResponseCode del(const char* filename, const BlobType type, uid_t userId);
- ResponseCode list(const android::String8& prefix, android::Vector<android::String16>* matches,
- uid_t userId);
+ std::tuple<ResponseCode, Blob, Blob> get(const LockedKeyBlobEntry& blobfile);
+ ResponseCode put(const LockedKeyBlobEntry& blobfile, Blob keyBlob, Blob characteristicsBlob);
+ ResponseCode del(const LockedKeyBlobEntry& blobfile);
- std::string addGrant(const char* alias, uid_t granterUid, uid_t granteeUid);
- bool removeGrant(const char* alias, const uid_t granterUid, const uid_t granteeUid);
+ std::string addGrant(const LockedKeyBlobEntry& blobfile, uid_t granteeUid);
+ bool removeGrant(const LockedKeyBlobEntry& blobfile, const uid_t granteeUid);
void removeAllGrantsToUid(const uid_t granteeUid);
- ResponseCode importKey(const uint8_t* key, size_t keyLen, const char* filename, uid_t userId,
- int32_t flags);
+ ResponseCode importKey(const uint8_t* key, size_t keyLen, const LockedKeyBlobEntry& blobfile,
+ uid_t userId, int32_t flags);
bool isHardwareBacked(const android::String16& keyType) const;
- ResponseCode getKeyForName(Blob* keyBlob, const android::String8& keyName, const uid_t uid,
- const BlobType type);
+ std::tuple<ResponseCode, Blob, Blob, LockedKeyBlobEntry>
+ getKeyForName(const android::String8& keyName, const uid_t uid, const BlobType type);
- /**
- * Returns any existing UserState or creates it if it doesn't exist.
- */
- UserState* getUserState(uid_t userId);
+ void binderDied(const ::android::wp<IBinder>& who) override;
- /**
- * Returns any existing UserState or creates it if it doesn't exist.
- */
- UserState* getUserStateByUid(uid_t uid);
-
- /**
- * Returns NULL if the UserState doesn't already exist.
- */
- const UserState* getUserState(uid_t userId) const;
-
- /**
- * Returns NULL if the UserState doesn't already exist.
- */
- const UserState* getUserStateByUid(uid_t uid) const;
+ UserStateDB& getUserStateDB() { return mUserStateDB; }
+ AuthTokenTable& getAuthTokenTable() { return mAuthTokenTable; }
+ KeystoreKeymasterEnforcement& getEnforcementPolicy() { return mEnforcementPolicy; }
+ ConfirmationManager& getConfirmationManager() { return *mConfirmationManager; }
private:
static const char* kOldMasterKey;
@@ -142,10 +150,14 @@
static const android::String16 kEcKeyType;
Entropy* mEntropy;
- KeymasterDevices mKmDevices;
+ KeymasterWorkers mKmDevices;
+
bool mAllowNewFallback;
- android::Vector<UserState*> mMasterKeys;
+ UserStateDB mUserStateDB;
+ AuthTokenTable mAuthTokenTable;
+ KeystoreKeymasterEnforcement mEnforcementPolicy;
+ sp<ConfirmationManager> mConfirmationManager;
::keystore::GrantStore mGrants;
@@ -156,15 +168,7 @@
/**
* Upgrade the key from the current version to whatever is newest.
*/
- bool upgradeBlob(const char* filename, Blob* blob, const uint8_t oldVersion,
- const BlobType type, uid_t uid);
-
- /**
- * Takes a blob that is an PEM-encoded RSA key as a byte array and converts it to a DER-encoded
- * PKCS#8 for import into a keymaster. Then it overwrites the original blob with the new blob
- * format that is returned from the keymaster.
- */
- ResponseCode importBlobAsKey(Blob* blob, const char* filename, uid_t uid);
+ bool upgradeBlob(Blob* blob, const uint8_t oldVersion);
void readMetaData();
void writeMetaData();