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/blob.cpp b/keystore/blob.cpp
index b901553..f08e08d 100644
--- a/keystore/blob.cpp
+++ b/keystore/blob.cpp
@@ -28,6 +28,15 @@
#include "keystore_utils.h"
+#include <openssl/evp.h>
+
+#include <istream>
+#include <ostream>
+#include <streambuf>
+#include <string>
+
+#include <android-base/logging.h>
+
namespace {
constexpr size_t kGcmIvSizeBytes = 96 / 8;
@@ -128,11 +137,60 @@
return ResponseCode::NO_ERROR;
}
+class ArrayStreamBuffer : public std::streambuf {
+ public:
+ template <typename T, size_t size> ArrayStreamBuffer(const T (&data)[size]) {
+ static_assert(sizeof(T) == 1, "Array element size too large");
+ std::streambuf::char_type* d = const_cast<std::streambuf::char_type*>(
+ reinterpret_cast<const std::streambuf::char_type*>(&data[0]));
+ setg(d, d, d + size);
+ setp(d, d + size);
+ }
+
+ protected:
+ pos_type seekoff(off_type off, std::ios_base::seekdir dir,
+ std::ios_base::openmode which = std::ios_base::in |
+ std::ios_base::out) override {
+ bool in = which & std::ios_base::in;
+ bool out = which & std::ios_base::out;
+ if ((!in && !out) || (in && out && dir == std::ios_base::cur)) return -1;
+ std::streambuf::char_type* newPosPtr;
+ switch (dir) {
+ case std::ios_base::beg:
+ newPosPtr = pbase();
+ break;
+ case std::ios_base::cur:
+ // if dir == cur then in xor out due to
+ // if ((!in && !out) || (in && out && dir == std::ios_base::cur)) return -1; above
+ if (in)
+ newPosPtr = gptr();
+ else
+ newPosPtr = pptr();
+ break;
+ case std::ios_base::end:
+ // in and out bounds are the same and cannot change, so we can take either range
+ // regardless of the value of "which"
+ newPosPtr = epptr();
+ break;
+ }
+ newPosPtr += off;
+ if (newPosPtr < pbase() || newPosPtr > epptr()) return -1;
+ if (in) {
+ gbump(newPosPtr - gptr());
+ }
+ if (out) {
+ pbump(newPosPtr - pptr());
+ }
+ return newPosPtr - pbase();
+ }
+};
+
} // namespace
Blob::Blob(const uint8_t* value, size_t valueLength, const uint8_t* info, uint8_t infoLength,
BlobType type) {
- memset(&mBlob, 0, sizeof(mBlob));
+ mBlob = std::make_unique<blobv3>();
+ memset(mBlob.get(), 0, sizeof(blobv3));
if (valueLength > kValueSize) {
valueLength = kValueSize;
ALOGW("Provided blob length too large");
@@ -141,44 +199,76 @@
infoLength = kValueSize - valueLength;
ALOGW("Provided info length too large");
}
- mBlob.length = valueLength;
- memcpy(mBlob.value, value, valueLength);
+ mBlob->length = valueLength;
+ memcpy(mBlob->value, value, valueLength);
- mBlob.info = infoLength;
- memcpy(mBlob.value + valueLength, info, infoLength);
+ mBlob->info = infoLength;
+ memcpy(mBlob->value + valueLength, info, infoLength);
- mBlob.version = CURRENT_BLOB_VERSION;
- mBlob.type = uint8_t(type);
+ mBlob->version = CURRENT_BLOB_VERSION;
+ mBlob->type = uint8_t(type);
if (type == TYPE_MASTER_KEY) {
- mBlob.flags = KEYSTORE_FLAG_ENCRYPTED;
+ mBlob->flags = KEYSTORE_FLAG_ENCRYPTED;
} else {
- mBlob.flags = KEYSTORE_FLAG_NONE;
+ mBlob->flags = KEYSTORE_FLAG_NONE;
}
}
Blob::Blob(blobv3 b) {
- mBlob = b;
+ mBlob = std::make_unique<blobv3>(b);
}
Blob::Blob() {
- memset(&mBlob, 0, sizeof(mBlob));
+ if (mBlob) *mBlob = {};
+}
+
+Blob::Blob(const Blob& rhs) {
+ if (rhs.mBlob) {
+ mBlob = std::make_unique<blobv3>(*rhs.mBlob);
+ }
+}
+
+Blob::Blob(Blob&& rhs) : mBlob(std::move(rhs.mBlob)) {}
+
+Blob& Blob::operator=(const Blob& rhs) {
+ if (&rhs != this) {
+ if (mBlob) *mBlob = {};
+ if (rhs) {
+ mBlob = std::make_unique<blobv3>(*rhs.mBlob);
+ } else {
+ mBlob = {};
+ }
+ }
+ return *this;
+}
+
+Blob& Blob::operator=(Blob&& rhs) {
+ if (mBlob) *mBlob = {};
+ mBlob = std::move(rhs.mBlob);
+ return *this;
+}
+
+template <typename BlobType> static bool rawBlobIsEncrypted(const BlobType& blob) {
+ if (blob.version < 2) return true;
+
+ return blob.flags & (KEYSTORE_FLAG_ENCRYPTED | KEYSTORE_FLAG_SUPER_ENCRYPTED);
}
bool Blob::isEncrypted() const {
- if (mBlob.version < 2) {
+ if (mBlob->version < 2) {
return true;
}
- return mBlob.flags & KEYSTORE_FLAG_ENCRYPTED;
+ return mBlob->flags & KEYSTORE_FLAG_ENCRYPTED;
}
bool Blob::isSuperEncrypted() const {
- return mBlob.flags & KEYSTORE_FLAG_SUPER_ENCRYPTED;
+ return mBlob->flags & KEYSTORE_FLAG_SUPER_ENCRYPTED;
}
bool Blob::isCriticalToDeviceEncryption() const {
- return mBlob.flags & KEYSTORE_FLAG_CRITICAL_TO_DEVICE_ENCRYPTION;
+ return mBlob->flags & KEYSTORE_FLAG_CRITICAL_TO_DEVICE_ENCRYPTION;
}
inline uint8_t setFlag(uint8_t flags, bool set, KeyStoreFlag flag) {
@@ -186,83 +276,106 @@
}
void Blob::setEncrypted(bool encrypted) {
- mBlob.flags = setFlag(mBlob.flags, encrypted, KEYSTORE_FLAG_ENCRYPTED);
+ mBlob->flags = setFlag(mBlob->flags, encrypted, KEYSTORE_FLAG_ENCRYPTED);
}
void Blob::setSuperEncrypted(bool superEncrypted) {
- mBlob.flags = setFlag(mBlob.flags, superEncrypted, KEYSTORE_FLAG_SUPER_ENCRYPTED);
+ mBlob->flags = setFlag(mBlob->flags, superEncrypted, KEYSTORE_FLAG_SUPER_ENCRYPTED);
}
void Blob::setCriticalToDeviceEncryption(bool critical) {
- mBlob.flags = setFlag(mBlob.flags, critical, KEYSTORE_FLAG_CRITICAL_TO_DEVICE_ENCRYPTION);
+ mBlob->flags = setFlag(mBlob->flags, critical, KEYSTORE_FLAG_CRITICAL_TO_DEVICE_ENCRYPTION);
}
void Blob::setFallback(bool fallback) {
if (fallback) {
- mBlob.flags |= KEYSTORE_FLAG_FALLBACK;
+ mBlob->flags |= KEYSTORE_FLAG_FALLBACK;
} else {
- mBlob.flags &= ~KEYSTORE_FLAG_FALLBACK;
+ mBlob->flags &= ~KEYSTORE_FLAG_FALLBACK;
}
}
-ResponseCode Blob::writeBlob(const std::string& filename, const uint8_t* aes_key, State state,
- Entropy* entropy) {
+static ResponseCode writeBlob(const std::string& filename, Blob blob, blobv3* rawBlob,
+ const uint8_t* aes_key, State state, Entropy* entropy) {
ALOGV("writing blob %s", filename.c_str());
- const size_t dataLength = mBlob.length;
- mBlob.length = htonl(mBlob.length);
+ const size_t dataLength = rawBlob->length;
+ rawBlob->length = htonl(rawBlob->length);
- if (isEncrypted() || isSuperEncrypted()) {
+ if (blob.isEncrypted() || blob.isSuperEncrypted()) {
if (state != STATE_NO_ERROR) {
ALOGD("couldn't insert encrypted blob while not unlocked");
return ResponseCode::LOCKED;
}
- memset(mBlob.initialization_vector, 0, AES_BLOCK_SIZE);
- if (!entropy->generate_random_data(mBlob.initialization_vector, kGcmIvSizeBytes)) {
+ memset(rawBlob->initialization_vector, 0, AES_BLOCK_SIZE);
+ if (!entropy->generate_random_data(rawBlob->initialization_vector, kGcmIvSizeBytes)) {
ALOGW("Could not read random data for: %s", filename.c_str());
return ResponseCode::SYSTEM_ERROR;
}
- auto rc = AES_gcm_encrypt(mBlob.value /* in */, mBlob.value /* out */, dataLength, aes_key,
- mBlob.initialization_vector, mBlob.aead_tag);
+ auto rc = AES_gcm_encrypt(rawBlob->value /* in */, rawBlob->value /* out */, dataLength,
+ aes_key, rawBlob->initialization_vector, rawBlob->aead_tag);
if (rc != ResponseCode::NO_ERROR) return rc;
}
- size_t fileLength = offsetof(blobv3, value) + dataLength + mBlob.info;
+ size_t fileLength = offsetof(blobv3, value) + dataLength + rawBlob->info;
- char tmpFileName[] = ".tmpXXXXXX";
- int out = TEMP_FAILURE_RETRY(mkstemp(tmpFileName));
+ int out =
+ TEMP_FAILURE_RETRY(open(filename.c_str(), O_WRONLY | O_TRUNC | O_CREAT, S_IRUSR | S_IWUSR));
if (out < 0) {
- ALOGW("could not open temporary file: %s: %s", tmpFileName, strerror(errno));
+ ALOGW("could not open file: %s: %s", filename.c_str(), strerror(errno));
return ResponseCode::SYSTEM_ERROR;
}
- const size_t writtenBytes = writeFully(out, (uint8_t*)&mBlob, fileLength);
+ const size_t writtenBytes = writeFully(out, reinterpret_cast<uint8_t*>(rawBlob), fileLength);
if (close(out) != 0) {
return ResponseCode::SYSTEM_ERROR;
}
if (writtenBytes != fileLength) {
ALOGW("blob not fully written %zu != %zu", writtenBytes, fileLength);
- unlink(tmpFileName);
- return ResponseCode::SYSTEM_ERROR;
- }
- if (rename(tmpFileName, filename.c_str()) == -1) {
- ALOGW("could not rename blob to %s: %s", filename.c_str(), strerror(errno));
+ unlink(filename.c_str());
return ResponseCode::SYSTEM_ERROR;
}
return ResponseCode::NO_ERROR;
}
+ResponseCode LockedKeyBlobEntry::writeBlobs(Blob keyBlob, Blob characteristicsBlob,
+ const uint8_t* aes_key, State state,
+ Entropy* entropy) const {
+ if (entry_ == nullptr) {
+ return ResponseCode::SYSTEM_ERROR;
+ }
+ ResponseCode rc;
+ if (keyBlob) {
+ blobv3* rawBlob = keyBlob.mBlob.get();
+ rc = writeBlob(entry_->getKeyBlobPath(), std::move(keyBlob), rawBlob, aes_key, state,
+ entropy);
+ if (rc != ResponseCode::NO_ERROR) {
+ return rc;
+ }
+ }
+
+ if (characteristicsBlob) {
+ blobv3* rawBlob = characteristicsBlob.mBlob.get();
+ rc = writeBlob(entry_->getCharacteristicsBlobPath(), std::move(characteristicsBlob),
+ rawBlob, aes_key, state, entropy);
+ }
+ return rc;
+}
+
ResponseCode Blob::readBlob(const std::string& filename, const uint8_t* aes_key, State state) {
+ ResponseCode rc;
ALOGV("reading blob %s", filename.c_str());
+ std::unique_ptr<blobv3> rawBlob = std::make_unique<blobv3>();
+
const int in = TEMP_FAILURE_RETRY(open(filename.c_str(), O_RDONLY));
if (in < 0) {
return (errno == ENOENT) ? ResponseCode::KEY_NOT_FOUND : ResponseCode::SYSTEM_ERROR;
}
// fileLength may be less than sizeof(mBlob)
- const size_t fileLength = readFully(in, (uint8_t*)&mBlob, sizeof(mBlob));
+ const size_t fileLength = readFully(in, (uint8_t*)rawBlob.get(), sizeof(blobv3));
if (close(in) != 0) {
return ResponseCode::SYSTEM_ERROR;
}
@@ -271,67 +384,329 @@
return ResponseCode::VALUE_CORRUPTED;
}
- if ((isEncrypted() || isSuperEncrypted())) {
- if (state == STATE_LOCKED) return ResponseCode::LOCKED;
+ if (rawBlobIsEncrypted(*rawBlob)) {
+ if (state == STATE_LOCKED) {
+ mBlob = std::move(rawBlob);
+ return ResponseCode::LOCKED;
+ }
if (state == STATE_UNINITIALIZED) return ResponseCode::UNINITIALIZED;
}
if (fileLength < offsetof(blobv3, value)) return ResponseCode::VALUE_CORRUPTED;
- if (mBlob.version == 3) {
- const ssize_t encryptedLength = ntohl(mBlob.length);
+ if (rawBlob->version == 3) {
+ const ssize_t encryptedLength = ntohl(rawBlob->length);
- if (isEncrypted() || isSuperEncrypted()) {
- auto rc = AES_gcm_decrypt(mBlob.value /* in */, mBlob.value /* out */, encryptedLength,
- aes_key, mBlob.initialization_vector, mBlob.aead_tag);
+ if (rawBlobIsEncrypted(*rawBlob)) {
+ rc = AES_gcm_decrypt(rawBlob->value /* in */, rawBlob->value /* out */, encryptedLength,
+ aes_key, rawBlob->initialization_vector, rawBlob->aead_tag);
if (rc != ResponseCode::NO_ERROR) return rc;
}
- } else if (mBlob.version < 3) {
- blobv2& blob = reinterpret_cast<blobv2&>(mBlob);
+ } else if (rawBlob->version < 3) {
+ blobv2& v2blob = reinterpret_cast<blobv2&>(*rawBlob);
const size_t headerLength = offsetof(blobv2, encrypted);
- const ssize_t encryptedLength = fileLength - headerLength - blob.info;
+ const ssize_t encryptedLength = fileLength - headerLength - v2blob.info;
if (encryptedLength < 0) return ResponseCode::VALUE_CORRUPTED;
- if (isEncrypted() || isSuperEncrypted()) {
+ if (rawBlobIsEncrypted(*rawBlob)) {
if (encryptedLength % AES_BLOCK_SIZE != 0) {
return ResponseCode::VALUE_CORRUPTED;
}
AES_KEY key;
AES_set_decrypt_key(aes_key, kAesKeySize * 8, &key);
- AES_cbc_encrypt(blob.encrypted, blob.encrypted, encryptedLength, &key, blob.vector,
- AES_DECRYPT);
+ AES_cbc_encrypt(v2blob.encrypted, v2blob.encrypted, encryptedLength, &key,
+ v2blob.vector, AES_DECRYPT);
key = {}; // clear key
uint8_t computedDigest[MD5_DIGEST_LENGTH];
ssize_t digestedLength = encryptedLength - MD5_DIGEST_LENGTH;
- MD5(blob.digested, digestedLength, computedDigest);
- if (memcmp(blob.digest, computedDigest, MD5_DIGEST_LENGTH) != 0) {
+ MD5(v2blob.digested, digestedLength, computedDigest);
+ if (memcmp(v2blob.digest, computedDigest, MD5_DIGEST_LENGTH) != 0) {
return ResponseCode::VALUE_CORRUPTED;
}
}
}
- const ssize_t maxValueLength = fileLength - offsetof(blobv3, value) - mBlob.info;
- mBlob.length = ntohl(mBlob.length);
- if (mBlob.length < 0 || mBlob.length > maxValueLength ||
- mBlob.length + mBlob.info + AES_BLOCK_SIZE > static_cast<ssize_t>(sizeof(mBlob.value))) {
+ const ssize_t maxValueLength = fileLength - offsetof(blobv3, value) - rawBlob->info;
+ rawBlob->length = ntohl(rawBlob->length);
+ if (rawBlob->length < 0 || rawBlob->length > maxValueLength ||
+ rawBlob->length + rawBlob->info + AES_BLOCK_SIZE >
+ static_cast<ssize_t>(sizeof(rawBlob->value))) {
return ResponseCode::VALUE_CORRUPTED;
}
- if (mBlob.info != 0 && mBlob.version < 3) {
+ if (rawBlob->info != 0 && rawBlob->version < 3) {
// move info from after padding to after data
- memmove(mBlob.value + mBlob.length, mBlob.value + maxValueLength, mBlob.info);
+ memmove(rawBlob->value + rawBlob->length, rawBlob->value + maxValueLength, rawBlob->info);
}
+ mBlob = std::move(rawBlob);
return ResponseCode::NO_ERROR;
}
+std::tuple<ResponseCode, Blob, Blob> LockedKeyBlobEntry::readBlobs(const uint8_t* aes_key,
+ State state) const {
+ std::tuple<ResponseCode, Blob, Blob> result;
+ auto& [rc, keyBlob, characteristicsBlob] = result;
+ if (entry_ == nullptr) return rc = ResponseCode::SYSTEM_ERROR, result;
+
+ rc = keyBlob.readBlob(entry_->getKeyBlobPath(), aes_key, state);
+ if (rc != ResponseCode::NO_ERROR && rc != ResponseCode::UNINITIALIZED) {
+ return result;
+ }
+
+ if (entry_->hasCharacteristicsBlob()) {
+ characteristicsBlob.readBlob(entry_->getCharacteristicsBlobPath(), aes_key, state);
+ }
+ return result;
+}
+
+ResponseCode LockedKeyBlobEntry::deleteBlobs() const {
+ if (entry_ == nullptr) return ResponseCode::NO_ERROR;
+
+ // always try to delete both
+ ResponseCode rc1 = (unlink(entry_->getKeyBlobPath().c_str()) && errno != ENOENT)
+ ? ResponseCode::SYSTEM_ERROR
+ : ResponseCode::NO_ERROR;
+ if (rc1 != ResponseCode::NO_ERROR) {
+ ALOGW("Failed to delete key blob file \"%s\"", entry_->getKeyBlobPath().c_str());
+ }
+ ResponseCode rc2 = (unlink(entry_->getCharacteristicsBlobPath().c_str()) && errno != ENOENT)
+ ? ResponseCode::SYSTEM_ERROR
+ : ResponseCode::NO_ERROR;
+ if (rc2 != ResponseCode::NO_ERROR) {
+ ALOGW("Failed to delete key characteristics file \"%s\"",
+ entry_->getCharacteristicsBlobPath().c_str());
+ }
+ // then report the first error that occured
+ if (rc1 != ResponseCode::NO_ERROR) return rc1;
+ return rc2;
+}
+
keystore::SecurityLevel Blob::getSecurityLevel() const {
- return keystore::flagsToSecurityLevel(mBlob.flags);
+ return keystore::flagsToSecurityLevel(mBlob->flags);
}
void Blob::setSecurityLevel(keystore::SecurityLevel secLevel) {
- mBlob.flags &= ~(KEYSTORE_FLAG_FALLBACK | KEYSTORE_FLAG_STRONGBOX);
- mBlob.flags |= keystore::securityLevelToFlags(secLevel);
+ mBlob->flags &= ~(KEYSTORE_FLAG_FALLBACK | KEYSTORE_FLAG_STRONGBOX);
+ mBlob->flags |= keystore::securityLevelToFlags(secLevel);
+}
+
+std::tuple<bool, keystore::AuthorizationSet, keystore::AuthorizationSet>
+Blob::getKeyCharacteristics() const {
+ std::tuple<bool, keystore::AuthorizationSet, keystore::AuthorizationSet> result;
+ auto& [success, hwEnforced, swEnforced] = result;
+ success = false;
+ ArrayStreamBuffer buf(mBlob->value);
+ std::istream in(&buf);
+
+ // only the characteristics cache has both sets
+ if (getType() == TYPE_KEY_CHARACTERISTICS_CACHE) {
+ hwEnforced.Deserialize(&in);
+ } else if (getType() != TYPE_KEY_CHARACTERISTICS) {
+ // if its not the cache and not the legacy characteristics file we have no business
+ // here
+ return result;
+ }
+ swEnforced.Deserialize(&in);
+ success = !in.bad();
+
+ return result;
+}
+bool Blob::putKeyCharacteristics(const keystore::AuthorizationSet& hwEnforced,
+ const keystore::AuthorizationSet& swEnforced) {
+ if (!mBlob) mBlob = std::make_unique<blobv3>();
+ mBlob->version = CURRENT_BLOB_VERSION;
+ ArrayStreamBuffer buf(mBlob->value);
+ std::ostream out(&buf);
+ hwEnforced.Serialize(&out);
+ swEnforced.Serialize(&out);
+ if (out.bad()) return false;
+ setType(TYPE_KEY_CHARACTERISTICS_CACHE);
+ mBlob->length = out.tellp();
+ return true;
+}
+
+void LockedKeyBlobEntry::put(const KeyBlobEntry& entry) {
+ std::unique_lock<std::mutex> lock(locked_blobs_mutex_);
+ locked_blobs_.erase(entry);
+ lock.unlock();
+ locked_blobs_mutex_cond_var_.notify_all();
+}
+
+LockedKeyBlobEntry::~LockedKeyBlobEntry() {
+ if (entry_ != nullptr) put(*entry_);
+}
+
+LockedKeyBlobEntry LockedKeyBlobEntry::get(KeyBlobEntry entry) {
+ std::unique_lock<std::mutex> lock(locked_blobs_mutex_);
+ locked_blobs_mutex_cond_var_.wait(
+ lock, [&] { return locked_blobs_.find(entry) == locked_blobs_.end(); });
+ auto [iterator, success] = locked_blobs_.insert(std::move(entry));
+ if (!success) return {};
+ return LockedKeyBlobEntry(*iterator);
+}
+
+std::set<KeyBlobEntry> LockedKeyBlobEntry::locked_blobs_;
+std::mutex LockedKeyBlobEntry::locked_blobs_mutex_;
+std::condition_variable LockedKeyBlobEntry::locked_blobs_mutex_cond_var_;
+
+/* Here is the encoding of key names. This is necessary in order to allow arbitrary
+ * characters in key names. Characters in [0-~] are not encoded. Others are encoded
+ * into two bytes. The first byte is one of [+-.] which represents the first
+ * two bits of the character. The second byte encodes the rest of the bits into
+ * [0-o]. Therefore in the worst case the length of a key gets doubled. Note
+ * that Base64 cannot be used here due to the need of prefix match on keys. */
+
+static std::string encodeKeyName(const std::string& keyName) {
+ std::string encodedName;
+ encodedName.reserve(keyName.size() * 2);
+ auto in = keyName.begin();
+ while (in != keyName.end()) {
+ if (*in < '0' || *in > '~') {
+ encodedName.append(1, '+' + (uint8_t(*in) >> 6));
+ encodedName.append(1, '0' + (*in & 0x3F));
+ } else {
+ encodedName.append(1, *in);
+ }
+ ++in;
+ }
+ return encodedName;
+}
+
+static std::string decodeKeyName(const std::string& encodedName) {
+ std::string decodedName;
+ decodedName.reserve(encodedName.size());
+ auto in = encodedName.begin();
+ bool multichar = false;
+ char c;
+ while (in != encodedName.end()) {
+ if (multichar) {
+ multichar = false;
+ decodedName.append(1, c | *in);
+ } else if (*in >= '+' && *in <= '.') {
+ multichar = true;
+ c = (*in - '+') << 6;
+ } else {
+ decodedName.append(1, *in);
+ }
+ ++in;
+ }
+ // mulitchars at the end get truncated
+ return decodedName;
+}
+
+std::string KeyBlobEntry::getKeyBlobBaseName() const {
+ std::stringstream s;
+ if (masterkey_) {
+ s << alias_;
+ } else {
+ s << uid_ << "_" << encodeKeyName(alias_);
+ }
+ return s.str();
+}
+
+std::string KeyBlobEntry::getKeyBlobPath() const {
+ std::stringstream s;
+ if (masterkey_) {
+ s << user_dir_ << "/" << alias_;
+ } else {
+ s << user_dir_ << "/" << uid_ << "_" << encodeKeyName(alias_);
+ }
+ return s.str();
+}
+
+std::string KeyBlobEntry::getCharacteristicsBlobBaseName() const {
+ std::stringstream s;
+ if (!masterkey_) s << "." << uid_ << "_chr_" << encodeKeyName(alias_);
+ return s.str();
+}
+
+std::string KeyBlobEntry::getCharacteristicsBlobPath() const {
+ std::stringstream s;
+ if (!masterkey_)
+ s << user_dir_ << "/"
+ << "." << uid_ << "_chr_" << encodeKeyName(alias_);
+ return s.str();
+}
+
+bool KeyBlobEntry::hasKeyBlob() const {
+ return !access(getKeyBlobPath().c_str(), R_OK | W_OK);
+}
+bool KeyBlobEntry::hasCharacteristicsBlob() const {
+ return !access(getCharacteristicsBlobPath().c_str(), R_OK | W_OK);
+}
+
+static std::tuple<bool, uid_t, std::string> filename2UidAlias(const std::string& filepath) {
+ std::tuple<bool, uid_t, std::string> result;
+
+ auto& [success, uid, alias] = result;
+
+ success = false;
+
+ auto filenamebase = filepath.find_last_of('/');
+ std::string filename =
+ filenamebase == std::string::npos ? filepath : filepath.substr(filenamebase + 1);
+
+ if (filename[0] == '.') return result;
+
+ auto sep = filename.find('_');
+ if (sep == std::string::npos) return result;
+
+ std::stringstream s(filename.substr(0, sep));
+ s >> uid;
+ if (!s) return result;
+
+ alias = decodeKeyName(filename.substr(sep + 1));
+ success = true;
+ return result;
+}
+
+std::tuple<ResponseCode, std::list<LockedKeyBlobEntry>>
+LockedKeyBlobEntry::list(const std::string& user_dir,
+ std::function<bool(uid_t, const std::string&)> filter) {
+ std::list<LockedKeyBlobEntry> matches;
+
+ // This is a fence against any concurrent database accesses during database iteration.
+ // Only the keystore thread can lock entries. So it cannot be starved
+ // by workers grabbing new individual locks. We just wait here until all
+ // workers have relinquished their locked files.
+ std::unique_lock<std::mutex> lock(locked_blobs_mutex_);
+ locked_blobs_mutex_cond_var_.wait(lock, [&] { return locked_blobs_.empty(); });
+
+ DIR* dir = opendir(user_dir.c_str());
+ if (!dir) {
+ ALOGW("can't open directory for user: %s", strerror(errno));
+ return std::tuple<ResponseCode, std::list<LockedKeyBlobEntry>&&>{ResponseCode::SYSTEM_ERROR,
+ std::move(matches)};
+ }
+
+ struct dirent* file;
+ while ((file = readdir(dir)) != nullptr) {
+ // We only care about files.
+ if (file->d_type != DT_REG) {
+ continue;
+ }
+
+ // Skip anything that starts with a "."
+ if (file->d_name[0] == '.') {
+ continue;
+ }
+
+ auto [success, uid, alias] = filename2UidAlias(file->d_name);
+
+ if (!success) {
+ ALOGW("could not parse key filename \"%s\"", file->d_name);
+ continue;
+ }
+
+ if (!filter(uid, alias)) continue;
+
+ auto [iterator, dummy] = locked_blobs_.emplace(alias, user_dir, uid);
+ matches.push_back(*iterator);
+ }
+ closedir(dir);
+ return std::tuple<ResponseCode, std::list<LockedKeyBlobEntry>&&>{ResponseCode::NO_ERROR,
+ std::move(matches)};
}