| /* |
| * 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 <dirent.h> |
| #include <fcntl.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <sys/stat.h> |
| |
| #include <openssl/evp.h> |
| |
| #include <cutils/log.h> |
| |
| #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<const char*>(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); |
| } |