/* * 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 <cutils/log.h> #include "blob.h" #include "entropy.h" #include "keystore_utils.h" Blob::Blob(const uint8_t* value, size_t valueLength, const uint8_t* info, uint8_t infoLength, BlobType type) { memset(&mBlob, 0, sizeof(mBlob)); if (valueLength > VALUE_SIZE) { valueLength = VALUE_SIZE; ALOGW("Provided blob length too large"); } if (infoLength + valueLength > VALUE_SIZE) { infoLength = VALUE_SIZE - 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(blob b) { mBlob = b; } Blob::Blob() { memset(&mBlob, 0, sizeof(mBlob)); } bool Blob::isEncrypted() const { if (mBlob.version < 2) { return true; } return mBlob.flags & KEYSTORE_FLAG_ENCRYPTED; } void Blob::setEncrypted(bool encrypted) { if (encrypted) { mBlob.flags |= KEYSTORE_FLAG_ENCRYPTED; } else { mBlob.flags &= ~KEYSTORE_FLAG_ENCRYPTED; } } void Blob::setFallback(bool fallback) { if (fallback) { mBlob.flags |= KEYSTORE_FLAG_FALLBACK; } else { mBlob.flags &= ~KEYSTORE_FLAG_FALLBACK; } } ResponseCode Blob::writeBlob(const char* filename, AES_KEY* aes_key, State state, Entropy* entropy) { ALOGV("writing blob %s", filename); if (isEncrypted()) { if (state != STATE_NO_ERROR) { ALOGD("couldn't insert encrypted blob while not unlocked"); return LOCKED; } if (!entropy->generate_random_data(mBlob.vector, AES_BLOCK_SIZE)) { ALOGW("Could not read random data for: %s", filename); return SYSTEM_ERROR; } } // data includes the value and the value's length size_t dataLength = mBlob.length + sizeof(mBlob.length); // pad data to the AES_BLOCK_SIZE size_t digestedLength = ((dataLength + AES_BLOCK_SIZE - 1) / AES_BLOCK_SIZE * AES_BLOCK_SIZE); // encrypted data includes the digest value size_t encryptedLength = digestedLength + MD5_DIGEST_LENGTH; // move info after space for padding memmove(&mBlob.encrypted[encryptedLength], &mBlob.value[mBlob.length], mBlob.info); // zero padding area memset(mBlob.value + mBlob.length, 0, digestedLength - dataLength); mBlob.length = htonl(mBlob.length); if (isEncrypted()) { MD5(mBlob.digested, digestedLength, mBlob.digest); uint8_t vector[AES_BLOCK_SIZE]; memcpy(vector, mBlob.vector, AES_BLOCK_SIZE); AES_cbc_encrypt(mBlob.encrypted, mBlob.encrypted, encryptedLength, aes_key, vector, AES_ENCRYPT); } size_t headerLength = (mBlob.encrypted - (uint8_t*)&mBlob); size_t fileLength = encryptedLength + headerLength + mBlob.info; const char* tmpFileName = ".tmp"; int out = TEMP_FAILURE_RETRY(open(tmpFileName, O_WRONLY | O_TRUNC | O_CREAT, S_IRUSR | S_IWUSR)); if (out < 0) { ALOGW("could not open file: %s: %s", tmpFileName, strerror(errno)); return SYSTEM_ERROR; } size_t writtenBytes = writeFully(out, (uint8_t*)&mBlob, fileLength); if (close(out) != 0) { return SYSTEM_ERROR; } if (writtenBytes != fileLength) { ALOGW("blob not fully written %zu != %zu", writtenBytes, fileLength); unlink(tmpFileName); return SYSTEM_ERROR; } if (rename(tmpFileName, filename) == -1) { ALOGW("could not rename blob to %s: %s", filename, strerror(errno)); return SYSTEM_ERROR; } return NO_ERROR; } ResponseCode Blob::readBlob(const char* filename, AES_KEY* aes_key, State state) { ALOGV("reading blob %s", filename); int in = TEMP_FAILURE_RETRY(open(filename, O_RDONLY)); if (in < 0) { return (errno == ENOENT) ? KEY_NOT_FOUND : SYSTEM_ERROR; } // fileLength may be less than sizeof(mBlob) since the in // memory version has extra padding to tolerate rounding up to // the AES_BLOCK_SIZE size_t fileLength = readFully(in, (uint8_t*)&mBlob, sizeof(mBlob)); if (close(in) != 0) { return SYSTEM_ERROR; } if (fileLength == 0) { return VALUE_CORRUPTED; } if (isEncrypted() && (state != STATE_NO_ERROR)) { return LOCKED; } size_t headerLength = (mBlob.encrypted - (uint8_t*)&mBlob); if (fileLength < headerLength) { return VALUE_CORRUPTED; } ssize_t encryptedLength = fileLength - (headerLength + mBlob.info); if (encryptedLength < 0) { return VALUE_CORRUPTED; } ssize_t digestedLength; if (isEncrypted()) { if (encryptedLength % AES_BLOCK_SIZE != 0) { return VALUE_CORRUPTED; } AES_cbc_encrypt(mBlob.encrypted, mBlob.encrypted, encryptedLength, aes_key, mBlob.vector, AES_DECRYPT); digestedLength = encryptedLength - MD5_DIGEST_LENGTH; uint8_t computedDigest[MD5_DIGEST_LENGTH]; MD5(mBlob.digested, digestedLength, computedDigest); if (memcmp(mBlob.digest, computedDigest, MD5_DIGEST_LENGTH) != 0) { return VALUE_CORRUPTED; } } else { digestedLength = encryptedLength; } ssize_t maxValueLength = digestedLength - sizeof(mBlob.length); mBlob.length = ntohl(mBlob.length); if (mBlob.length < 0 || mBlob.length > maxValueLength) { return VALUE_CORRUPTED; } if (mBlob.info != 0) { // move info from after padding to after data memmove(&mBlob.value[mBlob.length], &mBlob.value[maxValueLength], mBlob.info); } return ::NO_ERROR; }