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/keymaster_enforcement.cpp b/keystore/keymaster_enforcement.cpp
index 1a7fa80..a17cd94 100644
--- a/keystore/keymaster_enforcement.cpp
+++ b/keystore/keymaster_enforcement.cpp
@@ -34,49 +34,6 @@
namespace keystore {
-class AccessTimeMap {
- public:
- explicit AccessTimeMap(uint32_t max_size) : max_size_(max_size) {}
-
- /* If the key is found, returns true and fills \p last_access_time. If not found returns
- * false. */
- bool LastKeyAccessTime(km_id_t keyid, uint32_t* last_access_time) const;
-
- /* Updates the last key access time with the currentTime parameter. Adds the key if
- * needed, returning false if key cannot be added because list is full. */
- bool UpdateKeyAccessTime(km_id_t keyid, uint32_t current_time, uint32_t timeout);
-
- private:
- struct AccessTime {
- km_id_t keyid;
- uint32_t access_time;
- uint32_t timeout;
- };
- std::list<AccessTime> last_access_list_;
- const uint32_t max_size_;
-};
-
-class AccessCountMap {
- public:
- explicit AccessCountMap(uint32_t max_size) : max_size_(max_size) {}
-
- /* If the key is found, returns true and fills \p count. If not found returns
- * false. */
- bool KeyAccessCount(km_id_t keyid, uint32_t* count) const;
-
- /* Increments key access count, adding an entry if the key has never been used. Returns
- * false if the list has reached maximum size. */
- bool IncrementKeyAccessCount(km_id_t keyid);
-
- private:
- struct AccessCount {
- km_id_t keyid;
- uint64_t access_count;
- };
- std::list<AccessCount> access_count_list_;
- const uint32_t max_size_;
-};
-
bool is_public_key_algorithm(const AuthorizationSet& auth_set) {
auto algorithm = auth_set.GetTagValue(TAG_ALGORITHM);
return algorithm.isOk() &&
@@ -107,12 +64,9 @@
KeymasterEnforcement::KeymasterEnforcement(uint32_t max_access_time_map_size,
uint32_t max_access_count_map_size)
- : access_time_map_(new (std::nothrow) AccessTimeMap(max_access_time_map_size)),
- access_count_map_(new (std::nothrow) AccessCountMap(max_access_count_map_size)) {}
+ : access_time_map_(max_access_time_map_size), access_count_map_(max_access_count_map_size) {}
KeymasterEnforcement::~KeymasterEnforcement() {
- delete access_time_map_;
- delete access_count_map_;
}
ErrorCode KeymasterEnforcement::AuthorizeOperation(const KeyPurpose purpose, const km_id_t keyid,
@@ -390,24 +344,14 @@
return ErrorCode::CALLER_NONCE_PROHIBITED;
if (min_ops_timeout != UINT32_MAX) {
- if (!access_time_map_) {
- ALOGE("Rate-limited keys table not allocated. Rate-limited keys disabled");
- return ErrorCode::MEMORY_ALLOCATION_FAILED;
- }
-
- if (!access_time_map_->UpdateKeyAccessTime(keyid, get_current_time(), min_ops_timeout)) {
+ if (!access_time_map_.UpdateKeyAccessTime(keyid, get_current_time(), min_ops_timeout)) {
ALOGE("Rate-limited keys table full. Entries will time out.");
return ErrorCode::TOO_MANY_OPERATIONS;
}
}
if (update_access_count) {
- if (!access_count_map_) {
- ALOGE("Usage-count limited keys tabel not allocated. Count-limited keys disabled");
- return ErrorCode::MEMORY_ALLOCATION_FAILED;
- }
-
- if (!access_count_map_->IncrementKeyAccessCount(keyid)) {
+ if (!access_count_map_.IncrementKeyAccessCount(keyid)) {
ALOGE("Usage count-limited keys table full, until reboot.");
return ErrorCode::TOO_MANY_OPERATIONS;
}
@@ -428,35 +372,32 @@
};
/* static */
-bool KeymasterEnforcement::CreateKeyId(const hidl_vec<uint8_t>& key_blob, km_id_t* keyid) {
+std::optional<km_id_t> KeymasterEnforcement::CreateKeyId(const hidl_vec<uint8_t>& key_blob) {
EvpMdCtx ctx;
+ km_id_t keyid;
uint8_t hash[EVP_MAX_MD_SIZE];
unsigned int hash_len;
if (EVP_DigestInit_ex(ctx.get(), EVP_sha256(), nullptr /* ENGINE */) &&
EVP_DigestUpdate(ctx.get(), &key_blob[0], key_blob.size()) &&
EVP_DigestFinal_ex(ctx.get(), hash, &hash_len)) {
- assert(hash_len >= sizeof(*keyid));
- memcpy(keyid, hash, sizeof(*keyid));
- return true;
+ assert(hash_len >= sizeof(keyid));
+ memcpy(&keyid, hash, sizeof(keyid));
+ return keyid;
}
- return false;
+ return {};
}
bool KeymasterEnforcement::MinTimeBetweenOpsPassed(uint32_t min_time_between, const km_id_t keyid) {
- if (!access_time_map_) return false;
-
uint32_t last_access_time;
- if (!access_time_map_->LastKeyAccessTime(keyid, &last_access_time)) return true;
+ if (!access_time_map_.LastKeyAccessTime(keyid, &last_access_time)) return true;
return min_time_between <= static_cast<int64_t>(get_current_time()) - last_access_time;
}
bool KeymasterEnforcement::MaxUsesPerBootNotExceeded(const km_id_t keyid, uint32_t max_uses) {
- if (!access_count_map_) return false;
-
uint32_t key_access_count;
- if (!access_count_map_->KeyAccessCount(keyid, &key_access_count)) return true;
+ if (!access_count_map_.KeyAccessCount(keyid, &key_access_count)) return true;
return key_access_count < max_uses;
}
@@ -545,6 +486,7 @@
}
bool AccessTimeMap::LastKeyAccessTime(km_id_t keyid, uint32_t* last_access_time) const {
+ std::lock_guard<std::mutex> lock(list_lock_);
for (auto& entry : last_access_list_)
if (entry.keyid == keyid) {
*last_access_time = entry.access_time;
@@ -554,6 +496,7 @@
}
bool AccessTimeMap::UpdateKeyAccessTime(km_id_t keyid, uint32_t current_time, uint32_t timeout) {
+ std::lock_guard<std::mutex> lock(list_lock_);
for (auto iter = last_access_list_.begin(); iter != last_access_list_.end();) {
if (iter->keyid == keyid) {
iter->access_time = current_time;
@@ -579,6 +522,7 @@
}
bool AccessCountMap::KeyAccessCount(km_id_t keyid, uint32_t* count) const {
+ std::lock_guard<std::mutex> lock(list_lock_);
for (auto& entry : access_count_list_)
if (entry.keyid == keyid) {
*count = entry.access_count;
@@ -588,6 +532,7 @@
}
bool AccessCountMap::IncrementKeyAccessCount(km_id_t keyid) {
+ std::lock_guard<std::mutex> lock(list_lock_);
for (auto& entry : access_count_list_)
if (entry.keyid == keyid) {
// Note that the 'if' below will always be true because KM_TAG_MAX_USES_PER_BOOT is a