|  | /* | 
|  | * Copyright (C) 2015 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 "keystore" | 
|  |  | 
|  | #include <arpa/inet.h> | 
|  | #include <errno.h> | 
|  | #include <fcntl.h> | 
|  | #include <string.h> | 
|  |  | 
|  | #include <log/log.h> | 
|  |  | 
|  | #include "blob.h" | 
|  |  | 
|  | #include "keystore_utils.h" | 
|  |  | 
|  | #include <openssl/evp.h> | 
|  | #include <openssl/rand.h> | 
|  |  | 
|  | #include <istream> | 
|  | #include <ostream> | 
|  | #include <streambuf> | 
|  | #include <string> | 
|  |  | 
|  | #include <android-base/logging.h> | 
|  | #include <android-base/unique_fd.h> | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | constexpr size_t kGcmIvSizeBytes = 96 / 8; | 
|  |  | 
|  | #if defined(__clang__) | 
|  | #define OPTNONE __attribute__((optnone)) | 
|  | #elif defined(__GNUC__) | 
|  | #define OPTNONE __attribute__((optimize("O0"))) | 
|  | #else | 
|  | #error Need a definition for OPTNONE | 
|  | #endif | 
|  |  | 
|  | class ArrayEraser { | 
|  | public: | 
|  | ArrayEraser(uint8_t* arr, size_t size) : mArr(arr), mSize(size) {} | 
|  | OPTNONE ~ArrayEraser() { std::fill(mArr, mArr + mSize, 0); } | 
|  |  | 
|  | private: | 
|  | volatile uint8_t* mArr; | 
|  | size_t mSize; | 
|  | }; | 
|  |  | 
|  | /** | 
|  | * Returns a EVP_CIPHER appropriate for the given key, based on the key's size. | 
|  | */ | 
|  | const EVP_CIPHER* getAesCipherForKey(const std::vector<uint8_t>& key) { | 
|  | const EVP_CIPHER* cipher = EVP_aes_256_gcm(); | 
|  | if (key.size() == kAes128KeySizeBytes) { | 
|  | cipher = EVP_aes_128_gcm(); | 
|  | } | 
|  | return cipher; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Encrypt 'len' data at 'in' with AES-GCM, using 128-bit or 256-bit key at 'key', 96-bit IV at | 
|  | * 'iv' and write output to 'out' (which may be the same location as 'in') and 128-bit tag to | 
|  | * 'tag'. | 
|  | */ | 
|  | ResponseCode AES_gcm_encrypt(const uint8_t* in, uint8_t* out, size_t len, | 
|  | const std::vector<uint8_t>& key, const uint8_t* iv, uint8_t* tag) { | 
|  |  | 
|  | // There can be 128-bit and 256-bit keys | 
|  | const EVP_CIPHER* cipher = getAesCipherForKey(key); | 
|  |  | 
|  | bssl::UniquePtr<EVP_CIPHER_CTX> ctx(EVP_CIPHER_CTX_new()); | 
|  |  | 
|  | EVP_EncryptInit_ex(ctx.get(), cipher, nullptr /* engine */, key.data(), iv); | 
|  | EVP_CIPHER_CTX_set_padding(ctx.get(), 0 /* no padding needed with GCM */); | 
|  |  | 
|  | std::unique_ptr<uint8_t[]> out_tmp(new uint8_t[len]); | 
|  | uint8_t* out_pos = out_tmp.get(); | 
|  | int out_len; | 
|  |  | 
|  | EVP_EncryptUpdate(ctx.get(), out_pos, &out_len, in, len); | 
|  | out_pos += out_len; | 
|  | EVP_EncryptFinal_ex(ctx.get(), out_pos, &out_len); | 
|  | out_pos += out_len; | 
|  | if (out_pos - out_tmp.get() != static_cast<ssize_t>(len)) { | 
|  | ALOGD("Encrypted ciphertext is the wrong size, expected %zu, got %zd", len, | 
|  | out_pos - out_tmp.get()); | 
|  | return ResponseCode::SYSTEM_ERROR; | 
|  | } | 
|  |  | 
|  | std::copy(out_tmp.get(), out_pos, out); | 
|  | EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_GET_TAG, kGcmTagLength, tag); | 
|  |  | 
|  | return ResponseCode::NO_ERROR; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Decrypt 'len' data at 'in' with AES-GCM, using 128-bit or 256-bit key at 'key', 96-bit IV at | 
|  | * 'iv', checking 128-bit tag at 'tag' and writing plaintext to 'out'(which may be the same | 
|  | * location as 'in'). | 
|  | */ | 
|  | ResponseCode AES_gcm_decrypt(const uint8_t* in, uint8_t* out, size_t len, | 
|  | const std::vector<uint8_t> key, const uint8_t* iv, | 
|  | const uint8_t* tag) { | 
|  |  | 
|  | // There can be 128-bit and 256-bit keys | 
|  | const EVP_CIPHER* cipher = getAesCipherForKey(key); | 
|  |  | 
|  | bssl::UniquePtr<EVP_CIPHER_CTX> ctx(EVP_CIPHER_CTX_new()); | 
|  |  | 
|  | EVP_DecryptInit_ex(ctx.get(), cipher, nullptr /* engine */, key.data(), iv); | 
|  | EVP_CIPHER_CTX_set_padding(ctx.get(), 0 /* no padding needed with GCM */); | 
|  | EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_SET_TAG, kGcmTagLength, const_cast<uint8_t*>(tag)); | 
|  |  | 
|  | std::unique_ptr<uint8_t[]> out_tmp(new uint8_t[len]); | 
|  | ArrayEraser out_eraser(out_tmp.get(), len); | 
|  | uint8_t* out_pos = out_tmp.get(); | 
|  | int out_len; | 
|  |  | 
|  | EVP_DecryptUpdate(ctx.get(), out_pos, &out_len, in, len); | 
|  | out_pos += out_len; | 
|  | if (!EVP_DecryptFinal_ex(ctx.get(), out_pos, &out_len)) { | 
|  | ALOGE("Failed to decrypt blob; ciphertext or tag is likely corrupted"); | 
|  | return ResponseCode::VALUE_CORRUPTED; | 
|  | } | 
|  | out_pos += out_len; | 
|  | if (out_pos - out_tmp.get() != static_cast<ssize_t>(len)) { | 
|  | ALOGE("Encrypted plaintext is the wrong size, expected %zu, got %zd", len, | 
|  | out_pos - out_tmp.get()); | 
|  | return ResponseCode::VALUE_CORRUPTED; | 
|  | } | 
|  |  | 
|  | std::copy(out_tmp.get(), out_pos, out); | 
|  |  | 
|  | return ResponseCode::NO_ERROR; | 
|  | } | 
|  |  | 
|  | class ArrayStreamBuffer : public std::streambuf { | 
|  | public: | 
|  | template <typename T, size_t size> explicit 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) { | 
|  | mBlob = std::make_unique<blobv3>(); | 
|  | memset(mBlob.get(), 0, sizeof(blobv3)); | 
|  | if (valueLength > kValueSize) { | 
|  | valueLength = kValueSize; | 
|  | ALOGW("Provided blob length too large"); | 
|  | } | 
|  | if (infoLength + valueLength > kValueSize) { | 
|  | infoLength = kValueSize - valueLength; | 
|  | ALOGW("Provided info length too large"); | 
|  | } | 
|  | mBlob->length = valueLength; | 
|  | memcpy(mBlob->value, value, valueLength); | 
|  |  | 
|  | mBlob->info = infoLength; | 
|  | memcpy(mBlob->value + valueLength, info, infoLength); | 
|  |  | 
|  | mBlob->version = CURRENT_BLOB_VERSION; | 
|  | mBlob->type = uint8_t(type); | 
|  |  | 
|  | if (type == TYPE_MASTER_KEY) { | 
|  | mBlob->flags = KEYSTORE_FLAG_ENCRYPTED; | 
|  | } else { | 
|  | mBlob->flags = KEYSTORE_FLAG_NONE; | 
|  | } | 
|  | } | 
|  |  | 
|  | Blob::Blob(blobv3 b) { | 
|  | mBlob = std::make_unique<blobv3>(b); | 
|  | } | 
|  |  | 
|  | Blob::Blob() { | 
|  | 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) { | 
|  | return true; | 
|  | } | 
|  |  | 
|  | return mBlob->flags & KEYSTORE_FLAG_ENCRYPTED; | 
|  | } | 
|  |  | 
|  | bool Blob::isSuperEncrypted() const { | 
|  | return mBlob->flags & KEYSTORE_FLAG_SUPER_ENCRYPTED; | 
|  | } | 
|  |  | 
|  | bool Blob::isCriticalToDeviceEncryption() const { | 
|  | return mBlob->flags & KEYSTORE_FLAG_CRITICAL_TO_DEVICE_ENCRYPTION; | 
|  | } | 
|  |  | 
|  | inline uint8_t setFlag(uint8_t flags, bool set, KeyStoreFlag flag) { | 
|  | return set ? (flags | flag) : (flags & ~flag); | 
|  | } | 
|  |  | 
|  | void Blob::setEncrypted(bool 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); | 
|  | } | 
|  |  | 
|  | void Blob::setCriticalToDeviceEncryption(bool critical) { | 
|  | mBlob->flags = setFlag(mBlob->flags, critical, KEYSTORE_FLAG_CRITICAL_TO_DEVICE_ENCRYPTION); | 
|  | } | 
|  |  | 
|  | void Blob::setFallback(bool fallback) { | 
|  | if (fallback) { | 
|  | mBlob->flags |= KEYSTORE_FLAG_FALLBACK; | 
|  | } else { | 
|  | mBlob->flags &= ~KEYSTORE_FLAG_FALLBACK; | 
|  | } | 
|  | } | 
|  |  | 
|  | static ResponseCode writeBlob(const std::string& filename, Blob blob, blobv3* rawBlob, | 
|  | const std::vector<uint8_t>& aes_key, State state) { | 
|  | ALOGV("writing blob %s", filename.c_str()); | 
|  |  | 
|  | const size_t dataLength = rawBlob->length; | 
|  | rawBlob->length = htonl(rawBlob->length); | 
|  |  | 
|  | if (blob.isEncrypted() || blob.isSuperEncrypted()) { | 
|  | if (state != STATE_NO_ERROR) { | 
|  | ALOGD("couldn't insert encrypted blob while not unlocked"); | 
|  | return ResponseCode::LOCKED; | 
|  | } | 
|  |  | 
|  | memset(rawBlob->initialization_vector, 0, AES_BLOCK_SIZE); | 
|  | if (!RAND_bytes(rawBlob->initialization_vector, kGcmIvSizeBytes)) { | 
|  | ALOGW("Could not read random data for: %s", filename.c_str()); | 
|  | return ResponseCode::SYSTEM_ERROR; | 
|  | } | 
|  |  | 
|  | 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 + rawBlob->info; | 
|  |  | 
|  | char tmpFileName[] = ".tmpXXXXXX"; | 
|  | { | 
|  | android::base::unique_fd out(TEMP_FAILURE_RETRY(mkstemp(tmpFileName))); | 
|  | if (out < 0) { | 
|  | LOG(ERROR) << "could not open temp file: " << tmpFileName | 
|  | << " for writing blob file: " << filename.c_str() | 
|  | << " because: " << strerror(errno); | 
|  | return ResponseCode::SYSTEM_ERROR; | 
|  | } | 
|  |  | 
|  | const size_t writtenBytes = | 
|  | writeFully(out, reinterpret_cast<uint8_t*>(rawBlob), fileLength); | 
|  |  | 
|  | if (writtenBytes != fileLength) { | 
|  | LOG(ERROR) << "blob not fully written " << writtenBytes << " != " << fileLength; | 
|  | unlink(tmpFileName); | 
|  | return ResponseCode::SYSTEM_ERROR; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (rename(tmpFileName, filename.c_str()) == -1) { | 
|  | LOG(ERROR) << "could not rename blob file to " << filename | 
|  | << " because: " << strerror(errno); | 
|  | unlink(tmpFileName); | 
|  | return ResponseCode::SYSTEM_ERROR; | 
|  | } | 
|  |  | 
|  | fsyncDirectory(getContainingDirectory(filename)); | 
|  |  | 
|  | return ResponseCode::NO_ERROR; | 
|  | } | 
|  |  | 
|  | ResponseCode LockedKeyBlobEntry::writeBlobs(Blob keyBlob, Blob characteristicsBlob, | 
|  | const std::vector<uint8_t>& aes_key, | 
|  | State state) 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); | 
|  | 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); | 
|  | } | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | ResponseCode Blob::readBlob(const std::string& filename, const std::vector<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*)rawBlob.get(), sizeof(blobv3)); | 
|  | if (close(in) != 0) { | 
|  | return ResponseCode::SYSTEM_ERROR; | 
|  | } | 
|  |  | 
|  | if (fileLength == 0) { | 
|  | LOG(ERROR) << __func__ << " VALUE_CORRUPTED file length == 0"; | 
|  | return ResponseCode::VALUE_CORRUPTED; | 
|  | } | 
|  |  | 
|  | 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)) { | 
|  | LOG(ERROR) << __func__ << " VALUE_CORRUPTED blob file too short: " << fileLength; | 
|  | return ResponseCode::VALUE_CORRUPTED; | 
|  | } | 
|  |  | 
|  | if (rawBlob->version == 3) { | 
|  | const ssize_t encryptedLength = ntohl(rawBlob->length); | 
|  |  | 
|  | 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) { | 
|  | // If the blob was superencrypted and decryption failed, it is | 
|  | // almost certain that decryption is failing due to a user's | 
|  | // changed master key. | 
|  | if ((rawBlob->flags & KEYSTORE_FLAG_SUPER_ENCRYPTED) && | 
|  | (rc == ResponseCode::VALUE_CORRUPTED)) { | 
|  | return ResponseCode::KEY_PERMANENTLY_INVALIDATED; | 
|  | } | 
|  | LOG(ERROR) << __func__ << " AES_gcm_decrypt returned: " << uint32_t(rc); | 
|  |  | 
|  | return rc; | 
|  | } | 
|  | } | 
|  | } else if (rawBlob->version < 3) { | 
|  | blobv2& v2blob = reinterpret_cast<blobv2&>(*rawBlob); | 
|  | const size_t headerLength = offsetof(blobv2, encrypted); | 
|  | const ssize_t encryptedLength = fileLength - headerLength - v2blob.info; | 
|  | if (encryptedLength < 0) { | 
|  | LOG(ERROR) << __func__ << " VALUE_CORRUPTED v2blob file too short"; | 
|  | return ResponseCode::VALUE_CORRUPTED; | 
|  | } | 
|  |  | 
|  | if (rawBlobIsEncrypted(*rawBlob)) { | 
|  | if (encryptedLength % AES_BLOCK_SIZE != 0) { | 
|  | LOG(ERROR) << __func__ | 
|  | << " VALUE_CORRUPTED encrypted length is not a multiple" | 
|  | " of the AES block size"; | 
|  | return ResponseCode::VALUE_CORRUPTED; | 
|  | } | 
|  |  | 
|  | AES_KEY key; | 
|  | AES_set_decrypt_key(aes_key.data(), kAesKeySize * 8, &key); | 
|  | 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(v2blob.digested, digestedLength, computedDigest); | 
|  | if (memcmp(v2blob.digest, computedDigest, MD5_DIGEST_LENGTH) != 0) { | 
|  | LOG(ERROR) << __func__ << " v2blob MD5 digest mismatch"; | 
|  | return ResponseCode::VALUE_CORRUPTED; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | 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))) { | 
|  | LOG(ERROR) << __func__ << " raw blob length is out of bounds"; | 
|  | return ResponseCode::VALUE_CORRUPTED; | 
|  | } | 
|  |  | 
|  | if (rawBlob->info != 0 && rawBlob->version < 3) { | 
|  | // move info from after padding to after data | 
|  | 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 std::vector<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); | 
|  | } | 
|  |  | 
|  | void Blob::setSecurityLevel(keystore::SecurityLevel 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. */ | 
|  |  | 
|  | std::string encodeKeyName(const std::string& keyName) { | 
|  | std::string encodedName; | 
|  | encodedName.reserve(keyName.size() * 2); | 
|  | auto in = keyName.begin(); | 
|  | while (in != keyName.end()) { | 
|  | // Input character needs to be encoded. | 
|  | if (*in < '0' || *in > '~') { | 
|  | // Encode the two most-significant bits of the input char in the first | 
|  | // output character, by counting up from 43 ('+'). | 
|  | encodedName.append(1, '+' + (uint8_t(*in) >> 6)); | 
|  | // Encode the six least-significant bits of the input char in the second | 
|  | // output character, by counting up from 48 ('0'). | 
|  | // This is safe because the maximum value is 112, which is the | 
|  | // character 'p'. | 
|  | encodedName.append(1, '0' + (*in & 0x3F)); | 
|  | } else { | 
|  | // No need to encode input char - append as-is. | 
|  | encodedName.append(1, *in); | 
|  | } | 
|  | ++in; | 
|  | } | 
|  | return encodedName; | 
|  | } | 
|  |  | 
|  | 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) { | 
|  | // Second part of a multi-character encoding. Turn off the multichar | 
|  | // flag and set the six least-significant bits of c to the value originally | 
|  | // encoded by counting up from '0'. | 
|  | multichar = false; | 
|  | decodedName.append(1, c | (uint8_t(*in) - '0')); | 
|  | } else if (*in >= '+' && *in <= '.') { | 
|  | // First part of a multi-character encoding. Set the multichar flag | 
|  | // and set the two most-significant bits of c to be the two bits originally | 
|  | // encoded by counting up from '+'. | 
|  | multichar = true; | 
|  | c = (*in - '+') << 6; | 
|  | } else { | 
|  | // Regular character, append as-is. | 
|  | 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 { | 
|  | int trys = 3; | 
|  | while (trys--) { | 
|  | if (!access(getKeyBlobPath().c_str(), R_OK | W_OK)) return true; | 
|  | if (errno == ENOENT) return false; | 
|  | LOG(WARNING) << "access encountered " << strerror(errno) << " (" << errno << ")" | 
|  | << " while checking for key blob"; | 
|  | if (errno != EAGAIN) break; | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | bool KeyBlobEntry::hasCharacteristicsBlob() const { | 
|  | int trys = 3; | 
|  | while (trys--) { | 
|  | if (!access(getCharacteristicsBlobPath().c_str(), R_OK | W_OK)) return true; | 
|  | if (errno == ENOENT) return false; | 
|  | LOG(WARNING) << "access encountered " << strerror(errno) << " (" << errno << ")" | 
|  | << " while checking for key characteristics blob"; | 
|  | if (errno != EAGAIN) break; | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | 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)}; | 
|  | } |