/* * 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 "user_state.h" #include #include #include #include #include #include #include #include "blob.h" #include "keystore_utils.h" UserState::UserState(uid_t userId) : mUserId(userId), mRetry(MAX_RETRY) { asprintf(&mUserDir, "user_%u", mUserId); asprintf(&mMasterKeyFile, "%s/.masterkey", mUserDir); } UserState::~UserState() { free(mUserDir); free(mMasterKeyFile); } bool UserState::initialize() { if ((mkdir(mUserDir, S_IRUSR | S_IWUSR | S_IXUSR) < 0) && (errno != EEXIST)) { ALOGE("Could not create directory '%s'", mUserDir); return false; } if (access(mMasterKeyFile, R_OK) == 0) { setState(STATE_LOCKED); } else { setState(STATE_UNINITIALIZED); } return true; } void UserState::setState(State state) { mState = state; if (mState == STATE_NO_ERROR || mState == STATE_UNINITIALIZED) { mRetry = MAX_RETRY; } } void UserState::zeroizeMasterKeysInMemory() { memset(mMasterKey, 0, sizeof(mMasterKey)); memset(mSalt, 0, sizeof(mSalt)); memset(&mMasterKeyEncryption, 0, sizeof(mMasterKeyEncryption)); memset(&mMasterKeyDecryption, 0, sizeof(mMasterKeyDecryption)); } bool UserState::deleteMasterKey() { setState(STATE_UNINITIALIZED); zeroizeMasterKeysInMemory(); return unlink(mMasterKeyFile) == 0 || errno == ENOENT; } ResponseCode UserState::initialize(const android::String8& pw, Entropy* entropy) { if (!generateMasterKey(entropy)) { return SYSTEM_ERROR; } ResponseCode response = writeMasterKey(pw, entropy); if (response != NO_ERROR) { return response; } setupMasterKeys(); return ::NO_ERROR; } ResponseCode UserState::copyMasterKey(UserState* src) { if (mState != STATE_UNINITIALIZED) { return ::SYSTEM_ERROR; } if (src->getState() != STATE_NO_ERROR) { return ::SYSTEM_ERROR; } memcpy(mMasterKey, src->mMasterKey, MASTER_KEY_SIZE_BYTES); setupMasterKeys(); return copyMasterKeyFile(src); } ResponseCode UserState::copyMasterKeyFile(UserState* src) { /* Copy the master key file to the new user. Unfortunately we don't have the src user's * password so we cannot generate a new file with a new salt. */ int in = TEMP_FAILURE_RETRY(open(src->getMasterKeyFileName(), O_RDONLY)); if (in < 0) { return ::SYSTEM_ERROR; } blob rawBlob; size_t length = readFully(in, (uint8_t*)&rawBlob, sizeof(rawBlob)); if (close(in) != 0) { return ::SYSTEM_ERROR; } int out = TEMP_FAILURE_RETRY(open(mMasterKeyFile, O_WRONLY | O_TRUNC | O_CREAT, S_IRUSR | S_IWUSR)); if (out < 0) { return ::SYSTEM_ERROR; } size_t outLength = writeFully(out, (uint8_t*)&rawBlob, length); if (close(out) != 0) { return ::SYSTEM_ERROR; } if (outLength != length) { ALOGW("blob not fully written %zu != %zu", outLength, length); unlink(mMasterKeyFile); return ::SYSTEM_ERROR; } return ::NO_ERROR; } ResponseCode UserState::writeMasterKey(const android::String8& pw, Entropy* entropy) { uint8_t passwordKey[MASTER_KEY_SIZE_BYTES]; generateKeyFromPassword(passwordKey, MASTER_KEY_SIZE_BYTES, pw, mSalt); AES_KEY passwordAesKey; AES_set_encrypt_key(passwordKey, MASTER_KEY_SIZE_BITS, &passwordAesKey); Blob masterKeyBlob(mMasterKey, sizeof(mMasterKey), mSalt, sizeof(mSalt), TYPE_MASTER_KEY); return masterKeyBlob.writeBlob(mMasterKeyFile, &passwordAesKey, STATE_NO_ERROR, entropy); } ResponseCode UserState::readMasterKey(const android::String8& pw, Entropy* entropy) { int in = TEMP_FAILURE_RETRY(open(mMasterKeyFile, O_RDONLY)); if (in < 0) { return SYSTEM_ERROR; } // We read the raw blob to just to get the salt to generate the AES key, then we create the Blob // to use with decryptBlob blob rawBlob; size_t length = readFully(in, (uint8_t*)&rawBlob, sizeof(rawBlob)); if (close(in) != 0) { return SYSTEM_ERROR; } // find salt at EOF if present, otherwise we have an old file uint8_t* salt; if (length > SALT_SIZE && rawBlob.info == SALT_SIZE) { salt = (uint8_t*)&rawBlob + length - SALT_SIZE; } else { salt = NULL; } uint8_t passwordKey[MASTER_KEY_SIZE_BYTES]; generateKeyFromPassword(passwordKey, MASTER_KEY_SIZE_BYTES, pw, salt); AES_KEY passwordAesKey; AES_set_decrypt_key(passwordKey, MASTER_KEY_SIZE_BITS, &passwordAesKey); Blob masterKeyBlob(rawBlob); ResponseCode response = masterKeyBlob.readBlob(mMasterKeyFile, &passwordAesKey, STATE_NO_ERROR); if (response == SYSTEM_ERROR) { return response; } if (response == NO_ERROR && masterKeyBlob.getLength() == MASTER_KEY_SIZE_BYTES) { // If salt was missing, generate one and write a new master key file with the salt. if (salt == NULL) { if (!generateSalt(entropy)) { return SYSTEM_ERROR; } response = writeMasterKey(pw, entropy); } if (response == NO_ERROR) { memcpy(mMasterKey, masterKeyBlob.getValue(), MASTER_KEY_SIZE_BYTES); setupMasterKeys(); } return response; } if (mRetry <= 0) { reset(); return UNINITIALIZED; } --mRetry; switch (mRetry) { case 0: return WRONG_PASSWORD_0; case 1: return WRONG_PASSWORD_1; case 2: return WRONG_PASSWORD_2; case 3: return WRONG_PASSWORD_3; default: return WRONG_PASSWORD_3; } } bool UserState::reset() { DIR* dir = opendir(getUserDirName()); if (!dir) { // If the directory doesn't exist then nothing to do. if (errno == ENOENT) { return true; } ALOGW("couldn't open user directory: %s", strerror(errno)); return false; } struct dirent* file; while ((file = readdir(dir)) != NULL) { // skip . and .. if (!strcmp(".", file->d_name) || !strcmp("..", file->d_name)) { continue; } unlinkat(dirfd(dir), file->d_name, 0); } closedir(dir); return true; } void UserState::generateKeyFromPassword(uint8_t* key, ssize_t keySize, const android::String8& pw, uint8_t* salt) { size_t saltSize; if (salt != NULL) { saltSize = SALT_SIZE; } else { // Pre-gingerbread used this hardwired salt, readMasterKey will rewrite these when found salt = (uint8_t*)"keystore"; // sizeof = 9, not strlen = 8 saltSize = sizeof("keystore"); } PKCS5_PBKDF2_HMAC_SHA1(reinterpret_cast(pw.string()), pw.length(), salt, saltSize, 8192, keySize, key); } bool UserState::generateSalt(Entropy* entropy) { return entropy->generate_random_data(mSalt, sizeof(mSalt)); } bool UserState::generateMasterKey(Entropy* entropy) { if (!entropy->generate_random_data(mMasterKey, sizeof(mMasterKey))) { return false; } if (!generateSalt(entropy)) { return false; } return true; } void UserState::setupMasterKeys() { AES_set_encrypt_key(mMasterKey, MASTER_KEY_SIZE_BITS, &mMasterKeyEncryption); AES_set_decrypt_key(mMasterKey, MASTER_KEY_SIZE_BITS, &mMasterKeyDecryption); setState(STATE_NO_ERROR); }