dc4d142303
BoringSSL already provides C++ scopers. Test: mma Change-Id: I34d4ec36fc0b51750560be0886768a83fe69fbf5
512 lines
17 KiB
C++
512 lines
17 KiB
C++
/*
|
|
* Copyright (C) 2016 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 "KeyStore.h"
|
|
|
|
#include <dirent.h>
|
|
#include <fcntl.h>
|
|
|
|
#include <openssl/bio.h>
|
|
|
|
#include <utils/String16.h>
|
|
#include <utils/String8.h>
|
|
|
|
#include <android-base/scopeguard.h>
|
|
#include <android/hardware/keymaster/3.0/IKeymasterDevice.h>
|
|
#include <android/security/keystore/IKeystoreService.h>
|
|
#include <log/log_event_list.h>
|
|
|
|
#include <private/android_logger.h>
|
|
|
|
#include "keystore_utils.h"
|
|
#include "permissions.h"
|
|
#include <keystore/keystore_hidl_support.h>
|
|
|
|
#include "keymaster_worker.h"
|
|
|
|
namespace keystore {
|
|
|
|
const char* KeyStore::kOldMasterKey = ".masterkey";
|
|
const char* KeyStore::kMetaDataFile = ".metadata";
|
|
|
|
const android::String16 KeyStore::kRsaKeyType("RSA");
|
|
const android::String16 KeyStore::kEcKeyType("EC");
|
|
|
|
using android::String8;
|
|
|
|
KeyStore::KeyStore(const KeymasterDevices& kmDevices,
|
|
SecurityLevel minimalAllowedSecurityLevelForNewKeys)
|
|
: mAllowNewFallback(minimalAllowedSecurityLevelForNewKeys == SecurityLevel::SOFTWARE),
|
|
mConfirmationManager(new ConfirmationManager(this)) {
|
|
memset(&mMetaData, '\0', sizeof(mMetaData));
|
|
|
|
static_assert(std::tuple_size<std::decay_t<decltype(kmDevices)>>::value ==
|
|
std::tuple_size<decltype(mKmDevices)>::value,
|
|
"KmasterDevices and KeymasterWorkers must have the same size");
|
|
for (size_t i = 0; i < kmDevices.size(); ++i) {
|
|
if (kmDevices[SecurityLevel(i)]) {
|
|
mKmDevices[SecurityLevel(i)] =
|
|
std::make_shared<KeymasterWorker>(kmDevices[SecurityLevel(i)], this);
|
|
}
|
|
}
|
|
}
|
|
|
|
KeyStore::~KeyStore() {
|
|
}
|
|
|
|
ResponseCode KeyStore::initialize() {
|
|
readMetaData();
|
|
if (upgradeKeystore()) {
|
|
writeMetaData();
|
|
}
|
|
|
|
return ResponseCode::NO_ERROR;
|
|
}
|
|
|
|
ResponseCode KeyStore::initializeUser(const android::String8& pw, uid_t userId) {
|
|
auto userState = mUserStateDB.getUserState(userId);
|
|
return userState->initialize(pw);
|
|
}
|
|
|
|
ResponseCode KeyStore::copyMasterKey(uid_t srcUser, uid_t dstUser) {
|
|
auto userState = mUserStateDB.getUserState(dstUser);
|
|
auto initState = mUserStateDB.getUserState(srcUser);
|
|
return userState->copyMasterKey(&initState);
|
|
}
|
|
|
|
ResponseCode KeyStore::writeMasterKey(const android::String8& pw, uid_t userId) {
|
|
auto userState = mUserStateDB.getUserState(userId);
|
|
return userState->writeMasterKey(pw);
|
|
}
|
|
|
|
ResponseCode KeyStore::readMasterKey(const android::String8& pw, uid_t userId) {
|
|
auto userState = mUserStateDB.getUserState(userId);
|
|
return userState->readMasterKey(pw);
|
|
}
|
|
|
|
LockedKeyBlobEntry KeyStore::getLockedBlobEntryIfNotExists(const std::string& alias, uid_t uid) {
|
|
KeyBlobEntry kbe(alias, mUserStateDB.getUserStateByUid(uid)->getUserDirName(), uid);
|
|
auto result = LockedKeyBlobEntry::get(std::move(kbe));
|
|
if (result->hasKeyBlob()) return {};
|
|
return result;
|
|
}
|
|
|
|
std::optional<KeyBlobEntry> KeyStore::getBlobEntryIfExists(const std::string& alias, uid_t uid) {
|
|
KeyBlobEntry kbe(alias, mUserStateDB.getUserStateByUid(uid)->getUserDirName(), uid);
|
|
if (kbe.hasKeyBlob()) return kbe;
|
|
|
|
// If this is one of the legacy UID->UID mappings, use it.
|
|
uid_t euid = get_keystore_euid(uid);
|
|
if (euid != uid) {
|
|
kbe = KeyBlobEntry(alias, mUserStateDB.getUserStateByUid(euid)->getUserDirName(), euid);
|
|
if (kbe.hasKeyBlob()) return kbe;
|
|
}
|
|
|
|
// They might be using a granted key.
|
|
auto grant = mGrants.get(uid, alias);
|
|
if (grant) {
|
|
kbe = grant->entry_;
|
|
if (kbe.hasKeyBlob()) return kbe;
|
|
}
|
|
return {};
|
|
}
|
|
LockedKeyBlobEntry KeyStore::getLockedBlobEntryIfExists(const std::string& alias, uid_t uid) {
|
|
auto blobentry = getBlobEntryIfExists(alias, uid);
|
|
if (!blobentry) return {};
|
|
LockedKeyBlobEntry lockedentry = LockedKeyBlobEntry::get(std::move(*blobentry));
|
|
if (!lockedentry || !lockedentry->hasKeyBlob()) return {};
|
|
return lockedentry;
|
|
}
|
|
|
|
void KeyStore::resetUser(uid_t userId, bool keepUnenryptedEntries) {
|
|
android::String8 prefix("");
|
|
android::Vector<android::String16> aliases;
|
|
|
|
auto userState = mUserStateDB.getUserState(userId);
|
|
std::string userDirName = userState->getUserDirName();
|
|
auto encryptionKey = userState->getEncryptionKey();
|
|
auto state = userState->getState();
|
|
// userState is a proxy that holds a lock which may be required by a worker.
|
|
// LockedKeyBlobEntry::list has a fence that waits until all workers have finished which may
|
|
// not happen if a user state lock is held. The following line relinquishes the lock.
|
|
userState = {};
|
|
|
|
ResponseCode rc;
|
|
std::list<LockedKeyBlobEntry> matches;
|
|
|
|
// must not be called by a keymaster worker. List waits for workers to relinquish all access
|
|
// to blob entries
|
|
std::tie(rc, matches) = LockedKeyBlobEntry::list(userDirName);
|
|
if (rc != ResponseCode::NO_ERROR) {
|
|
return;
|
|
}
|
|
|
|
for (LockedKeyBlobEntry& lockedEntry : matches) {
|
|
bool shouldDelete = true;
|
|
|
|
if (keepUnenryptedEntries) {
|
|
Blob blob;
|
|
Blob charBlob;
|
|
ResponseCode rc;
|
|
|
|
std::tie(rc, blob, charBlob) = lockedEntry.readBlobs(encryptionKey, state);
|
|
|
|
switch (rc) {
|
|
case ResponseCode::SYSTEM_ERROR:
|
|
case ResponseCode::VALUE_CORRUPTED:
|
|
// If we can't read blobs, delete them.
|
|
shouldDelete = true;
|
|
break;
|
|
|
|
case ResponseCode::NO_ERROR:
|
|
case ResponseCode::LOCKED:
|
|
// Delete encrypted blobs but keep unencrypted blobs and super-encrypted blobs. We
|
|
// need to keep super-encrypted blobs so we can report that the user is
|
|
// unauthenticated if a caller tries to use them, rather than reporting that they
|
|
// don't exist.
|
|
shouldDelete = blob.isEncrypted();
|
|
break;
|
|
|
|
default:
|
|
ALOGE("Got unexpected return code %d from readBlobs", rc);
|
|
// This shouldn't happen. To be on the safe side, delete it.
|
|
shouldDelete = true;
|
|
break;
|
|
}
|
|
}
|
|
if (shouldDelete) {
|
|
del(lockedEntry);
|
|
}
|
|
}
|
|
|
|
userState = mUserStateDB.getUserState(userId);
|
|
if (!userState->deleteMasterKey()) {
|
|
ALOGE("Failed to delete user %d's master key", userId);
|
|
}
|
|
if (!keepUnenryptedEntries) {
|
|
if (!userState->reset()) {
|
|
ALOGE("Failed to remove user %d's directory", userId);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool KeyStore::isEmpty(uid_t userId) const {
|
|
std::string userDirName;
|
|
{
|
|
// userState holds a lock which must be relinquished before list is called. This scope
|
|
// prevents deadlocks.
|
|
auto userState = mUserStateDB.getUserState(userId);
|
|
if (!userState) {
|
|
return true;
|
|
}
|
|
userDirName = userState->getUserDirName();
|
|
}
|
|
|
|
ResponseCode rc;
|
|
std::list<LockedKeyBlobEntry> matches;
|
|
|
|
// must not be called by a keymaster worker. List waits for workers to relinquish all access
|
|
// to blob entries
|
|
std::tie(rc, matches) = LockedKeyBlobEntry::list(userDirName);
|
|
|
|
return rc == ResponseCode::SYSTEM_ERROR || matches.size() == 0;
|
|
}
|
|
|
|
void KeyStore::lock(uid_t userId) {
|
|
auto userState = mUserStateDB.getUserState(userId);
|
|
userState->zeroizeMasterKeysInMemory();
|
|
userState->setState(STATE_LOCKED);
|
|
}
|
|
|
|
static void maybeLogKeyIntegrityViolation(const LockedKeyBlobEntry& lockedEntry,
|
|
const BlobType type) {
|
|
if (!__android_log_security() || (type != TYPE_KEY_PAIR && type != TYPE_KEYMASTER_10)) return;
|
|
log_key_integrity_violation(lockedEntry->alias().c_str(), lockedEntry->uid());
|
|
}
|
|
|
|
std::tuple<ResponseCode, Blob, Blob> KeyStore::get(const LockedKeyBlobEntry& blobfile) {
|
|
std::tuple<ResponseCode, Blob, Blob> result;
|
|
|
|
uid_t userId = get_user_id(blobfile->uid());
|
|
Blob& keyBlob = std::get<1>(result);
|
|
ResponseCode& rc = std::get<0>(result);
|
|
|
|
auto userState = mUserStateDB.getUserState(userId);
|
|
BlobType type = BlobType::TYPE_ANY;
|
|
auto logOnScopeExit = android::base::make_scope_guard([&] {
|
|
if (rc == ResponseCode::VALUE_CORRUPTED) {
|
|
maybeLogKeyIntegrityViolation(blobfile, type);
|
|
}
|
|
});
|
|
|
|
result = blobfile.readBlobs(userState->getEncryptionKey(), userState->getState());
|
|
if (rc != ResponseCode::NO_ERROR) {
|
|
return result;
|
|
}
|
|
|
|
// update the type for logging (see scope_guard above)
|
|
type = keyBlob.getType();
|
|
|
|
const uint8_t version = keyBlob.getVersion();
|
|
if (version < CURRENT_BLOB_VERSION) {
|
|
/* If we upgrade the key, we need to write it to disk again. Then
|
|
* it must be read it again since the blob is encrypted each time
|
|
* it's written.
|
|
*/
|
|
if (upgradeBlob(&keyBlob, version)) {
|
|
if ((rc = this->put(blobfile, keyBlob, {})) != ResponseCode::NO_ERROR ||
|
|
(result = blobfile.readBlobs(userState->getEncryptionKey(), userState->getState()),
|
|
rc) != ResponseCode::NO_ERROR) {
|
|
return result;
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
ResponseCode KeyStore::put(const LockedKeyBlobEntry& blobfile, Blob keyBlob,
|
|
Blob characteristicsBlob) {
|
|
auto userState = mUserStateDB.getUserStateByUid(blobfile->uid());
|
|
return blobfile.writeBlobs(std::move(keyBlob), std::move(characteristicsBlob),
|
|
userState->getEncryptionKey(), userState->getState());
|
|
}
|
|
|
|
ResponseCode KeyStore::del(const LockedKeyBlobEntry& blobfile) {
|
|
Blob keyBlob;
|
|
Blob charactaristicsBlob;
|
|
ResponseCode rc;
|
|
uid_t uid = blobfile->uid();
|
|
std::string alias = blobfile->alias();
|
|
|
|
std::tie(rc, keyBlob, charactaristicsBlob) = get(blobfile);
|
|
|
|
// after getting the blob from the file system we scrub the filesystem.
|
|
mGrants.removeAllGrantsToKey(uid, alias);
|
|
auto result = blobfile.deleteBlobs();
|
|
|
|
if (rc != ResponseCode::NO_ERROR) {
|
|
LOG(ERROR) << "get keyblob failed " << int(rc);
|
|
return rc;
|
|
}
|
|
|
|
// if we got the blob successfully, we try and delete it from the keymaster device
|
|
auto dev = getDevice(keyBlob);
|
|
|
|
if (keyBlob.getType() == ::TYPE_KEYMASTER_10) {
|
|
dev->deleteKey(blob2hidlVec(keyBlob), [dev, alias, uid](Return<ErrorCode> rc) {
|
|
auto ret = KS_HANDLE_HIDL_ERROR(dev, rc);
|
|
// A device doesn't have to implement delete_key.
|
|
bool success = ret == ErrorCode::OK || ret == ErrorCode::UNIMPLEMENTED;
|
|
if (__android_log_security()) {
|
|
android_log_event_list(SEC_TAG_KEY_DESTROYED)
|
|
<< int32_t(success) << alias << int32_t(uid) << LOG_ID_SECURITY;
|
|
}
|
|
if (!success) {
|
|
LOG(ERROR) << "Keymaster delete for key " << alias << " of uid " << uid
|
|
<< " failed";
|
|
}
|
|
});
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
std::string KeyStore::addGrant(const LockedKeyBlobEntry& blobfile, uid_t granteeUid) {
|
|
return mGrants.put(granteeUid, blobfile);
|
|
}
|
|
|
|
bool KeyStore::removeGrant(const LockedKeyBlobEntry& blobfile, const uid_t granteeUid) {
|
|
return mGrants.removeByFileAlias(granteeUid, blobfile);
|
|
}
|
|
void KeyStore::removeAllGrantsToUid(const uid_t granteeUid) {
|
|
mGrants.removeAllGrantsToUid(granteeUid);
|
|
}
|
|
|
|
bool KeyStore::isHardwareBacked(const android::String16& keyType) const {
|
|
// if strongbox device is present TEE must also be present and of sufficiently high version
|
|
// to support all keys in hardware
|
|
if (getDevice(SecurityLevel::STRONGBOX)) return true;
|
|
if (!getDevice(SecurityLevel::TRUSTED_ENVIRONMENT)) {
|
|
ALOGW("can't get keymaster device");
|
|
return false;
|
|
}
|
|
|
|
auto version = getDevice(SecurityLevel::TRUSTED_ENVIRONMENT)->halVersion();
|
|
if (keyType == kRsaKeyType) return true; // All versions support RSA
|
|
return keyType == kEcKeyType && version.supportsEc;
|
|
}
|
|
|
|
std::tuple<ResponseCode, Blob, Blob, LockedKeyBlobEntry>
|
|
KeyStore::getKeyForName(const android::String8& keyName, const uid_t uid, const BlobType type) {
|
|
std::tuple<ResponseCode, Blob, Blob, LockedKeyBlobEntry> result;
|
|
auto& [rc, keyBlob, charBlob, lockedEntry] = result;
|
|
|
|
lockedEntry = getLockedBlobEntryIfExists(keyName.string(), uid);
|
|
|
|
if (!lockedEntry) return rc = ResponseCode::KEY_NOT_FOUND, std::move(result);
|
|
|
|
std::tie(rc, keyBlob, charBlob) = get(lockedEntry);
|
|
|
|
if (rc == ResponseCode::NO_ERROR) {
|
|
if (keyBlob.getType() != type) return rc = ResponseCode::KEY_NOT_FOUND, std::move(result);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
bool KeyStore::upgradeBlob(Blob* blob, const uint8_t oldVersion) {
|
|
bool updated = false;
|
|
uint8_t version = oldVersion;
|
|
|
|
if (!blob || !(*blob)) return false;
|
|
|
|
/* From V0 -> V1: All old types were unknown */
|
|
if (version == 0) {
|
|
ALOGE("Failed to upgrade key blob. Ancient blob version 0 is no longer supported");
|
|
|
|
return false;
|
|
}
|
|
|
|
/* From V1 -> V2: All old keys were encrypted */
|
|
if (version == 1) {
|
|
ALOGV("upgrading to version 2");
|
|
|
|
blob->setEncrypted(true);
|
|
version = 2;
|
|
updated = true;
|
|
}
|
|
|
|
/*
|
|
* If we've updated, set the key blob to the right version
|
|
* and write it.
|
|
*/
|
|
if (updated) {
|
|
blob->setVersion(version);
|
|
}
|
|
|
|
return updated;
|
|
}
|
|
|
|
void KeyStore::readMetaData() {
|
|
int in = TEMP_FAILURE_RETRY(open(kMetaDataFile, O_RDONLY));
|
|
if (in < 0) {
|
|
return;
|
|
}
|
|
size_t fileLength = readFully(in, (uint8_t*)&mMetaData, sizeof(mMetaData));
|
|
if (fileLength != sizeof(mMetaData)) {
|
|
ALOGI("Metadata file is %zd bytes (%zd experted); upgrade?", fileLength, sizeof(mMetaData));
|
|
}
|
|
close(in);
|
|
}
|
|
|
|
void KeyStore::writeMetaData() {
|
|
const char* tmpFileName = ".metadata.tmp";
|
|
int out =
|
|
TEMP_FAILURE_RETRY(open(tmpFileName, O_WRONLY | O_TRUNC | O_CREAT, S_IRUSR | S_IWUSR));
|
|
if (out < 0) {
|
|
ALOGE("couldn't write metadata file: %s", strerror(errno));
|
|
return;
|
|
}
|
|
size_t fileLength = writeFully(out, (uint8_t*)&mMetaData, sizeof(mMetaData));
|
|
if (fileLength != sizeof(mMetaData)) {
|
|
ALOGI("Could only write %zd bytes to metadata file (%zd expected)", fileLength,
|
|
sizeof(mMetaData));
|
|
}
|
|
close(out);
|
|
rename(tmpFileName, kMetaDataFile);
|
|
}
|
|
|
|
bool KeyStore::upgradeKeystore() {
|
|
bool upgraded = false;
|
|
|
|
if (mMetaData.version == 0) {
|
|
auto userState = getUserStateDB().getUserStateByUid(0);
|
|
|
|
// Initialize first so the directory is made.
|
|
userState->initialize();
|
|
|
|
// Migrate the old .masterkey file to user 0.
|
|
if (access(kOldMasterKey, R_OK) == 0) {
|
|
if (rename(kOldMasterKey, userState->getMasterKeyFileName().c_str()) < 0) {
|
|
ALOGE("couldn't migrate old masterkey: %s", strerror(errno));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Initialize again in case we had a key.
|
|
userState->initialize();
|
|
|
|
// Try to migrate existing keys.
|
|
DIR* dir = opendir(".");
|
|
if (!dir) {
|
|
// Give up now; maybe we can upgrade later.
|
|
ALOGE("couldn't open keystore's directory; something is wrong");
|
|
return false;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
// Find the current file's user.
|
|
char* end;
|
|
unsigned long thisUid = strtoul(file->d_name, &end, 10);
|
|
if (end[0] != '_' || end[1] == 0) {
|
|
continue;
|
|
}
|
|
auto otherUser = getUserStateDB().getUserStateByUid(thisUid);
|
|
if (otherUser->getUserId() != 0) {
|
|
unlinkat(dirfd(dir), file->d_name, 0);
|
|
}
|
|
|
|
// Rename the file into user directory.
|
|
DIR* otherdir = opendir(otherUser->getUserDirName().c_str());
|
|
if (otherdir == nullptr) {
|
|
ALOGW("couldn't open user directory for rename");
|
|
continue;
|
|
}
|
|
if (renameat(dirfd(dir), file->d_name, dirfd(otherdir), file->d_name) < 0) {
|
|
ALOGW("couldn't rename blob: %s: %s", file->d_name, strerror(errno));
|
|
}
|
|
closedir(otherdir);
|
|
}
|
|
closedir(dir);
|
|
|
|
mMetaData.version = 1;
|
|
upgraded = true;
|
|
}
|
|
|
|
return upgraded;
|
|
}
|
|
|
|
void KeyStore::binderDied(const ::android::wp<IBinder>& who) {
|
|
for (unsigned i = 0; i < mKmDevices.size(); ++i) {
|
|
if (mKmDevices[SecurityLevel(i)]) mKmDevices[SecurityLevel(i)]->binderDied(who);
|
|
}
|
|
getConfirmationManager().binderDied(who);
|
|
}
|
|
|
|
} // namespace keystore
|