blob: 625d0576d9a6c90300a8246e244f8110cd5141f2 [file] [log] [blame]
/*
* 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"
namespace {
constexpr size_t kGcmIvSizeBytes = 96 / 8;
template <typename T, void (*FreeFunc)(T*)> struct OpenSslObjectDeleter {
void operator()(T* p) { FreeFunc(p); }
};
#define DEFINE_OPENSSL_OBJECT_POINTER(name) \
typedef OpenSslObjectDeleter<name, name##_free> name##_Delete; \
typedef std::unique_ptr<name, name##_Delete> name##_Ptr;
DEFINE_OPENSSL_OBJECT_POINTER(EVP_CIPHER_CTX);
#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;
};
/*
* Encrypt 'len' data at 'in' with AES-GCM, using 128-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 uint8_t* key,
const uint8_t* iv, uint8_t* tag) {
const EVP_CIPHER* cipher = EVP_aes_128_gcm();
EVP_CIPHER_CTX_Ptr ctx(EVP_CIPHER_CTX_new());
EVP_EncryptInit_ex(ctx.get(), cipher, nullptr /* engine */, key, 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 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 uint8_t* key,
const uint8_t* iv, const uint8_t* tag) {
const EVP_CIPHER* cipher = EVP_aes_128_gcm();
EVP_CIPHER_CTX_Ptr ctx(EVP_CIPHER_CTX_new());
EVP_DecryptInit_ex(ctx.get(), cipher, nullptr /* engine */, key, 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)) {
ALOGD("Failed to decrypt blob; ciphertext or tag is likely corrupted");
return ResponseCode::SYSTEM_ERROR;
}
out_pos += out_len;
if (out_pos - out_tmp.get() != static_cast<ssize_t>(len)) {
ALOGD("Encrypted plaintext 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);
return ResponseCode::NO_ERROR;
}
} // namespace
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 > 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 = b;
}
Blob::Blob() {
memset(&mBlob, 0, sizeof(mBlob));
}
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;
}
}
ResponseCode Blob::writeBlob(const std::string& filename, const uint8_t* aes_key, State state,
Entropy* entropy) {
ALOGV("writing blob %s", filename.c_str());
const size_t dataLength = mBlob.length;
mBlob.length = htonl(mBlob.length);
if (isEncrypted() || isSuperEncrypted()) {
if (state != STATE_NO_ERROR) {
ALOGD("couldn't insert encrypted blob while not unlocked");
return ResponseCode::LOCKED;
}
memset(mBlob.initialization_vector, 0, AES_BLOCK_SIZE);
if (!entropy->generate_random_data(mBlob.initialization_vector, kGcmIvSizeBytes)) {
ALOGW("Could not read random data for: %s", filename.c_str());
return ResponseCode::SYSTEM_ERROR;
}
auto rc = AES_gcm_encrypt(mBlob.value /* in */, mBlob.value /* out */, dataLength, aes_key,
mBlob.initialization_vector, mBlob.aead_tag);
if (rc != ResponseCode::NO_ERROR) return rc;
}
size_t fileLength = offsetof(blobv3, value) + dataLength + 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 ResponseCode::SYSTEM_ERROR;
}
const size_t writtenBytes = writeFully(out, (uint8_t*)&mBlob, fileLength);
if (close(out) != 0) {
return ResponseCode::SYSTEM_ERROR;
}
if (writtenBytes != fileLength) {
ALOGW("blob not fully written %zu != %zu", writtenBytes, fileLength);
unlink(tmpFileName);
return ResponseCode::SYSTEM_ERROR;
}
if (rename(tmpFileName, filename.c_str()) == -1) {
ALOGW("could not rename blob to %s: %s", filename.c_str(), strerror(errno));
return ResponseCode::SYSTEM_ERROR;
}
return ResponseCode::NO_ERROR;
}
ResponseCode Blob::readBlob(const std::string& filename, const uint8_t* aes_key, State state) {
ALOGV("reading blob %s", filename.c_str());
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*)&mBlob, sizeof(mBlob));
if (close(in) != 0) {
return ResponseCode::SYSTEM_ERROR;
}
if (fileLength == 0) {
return ResponseCode::VALUE_CORRUPTED;
}
if ((isEncrypted() || isSuperEncrypted())) {
if (state == STATE_LOCKED) return ResponseCode::LOCKED;
if (state == STATE_UNINITIALIZED) return ResponseCode::UNINITIALIZED;
}
if (fileLength < offsetof(blobv3, value)) return ResponseCode::VALUE_CORRUPTED;
if (mBlob.version == 3) {
const ssize_t encryptedLength = ntohl(mBlob.length);
if (isEncrypted() || isSuperEncrypted()) {
auto rc = AES_gcm_decrypt(mBlob.value /* in */, mBlob.value /* out */, encryptedLength,
aes_key, mBlob.initialization_vector, mBlob.aead_tag);
if (rc != ResponseCode::NO_ERROR) return rc;
}
} else if (mBlob.version < 3) {
blobv2& blob = reinterpret_cast<blobv2&>(mBlob);
const size_t headerLength = offsetof(blobv2, encrypted);
const ssize_t encryptedLength = fileLength - headerLength - blob.info;
if (encryptedLength < 0) return ResponseCode::VALUE_CORRUPTED;
if (isEncrypted() || isSuperEncrypted()) {
if (encryptedLength % AES_BLOCK_SIZE != 0) {
return ResponseCode::VALUE_CORRUPTED;
}
AES_KEY key;
AES_set_decrypt_key(aes_key, kAesKeySize * 8, &key);
AES_cbc_encrypt(blob.encrypted, blob.encrypted, encryptedLength, &key, blob.vector,
AES_DECRYPT);
key = {}; // clear key
uint8_t computedDigest[MD5_DIGEST_LENGTH];
ssize_t digestedLength = encryptedLength - MD5_DIGEST_LENGTH;
MD5(blob.digested, digestedLength, computedDigest);
if (memcmp(blob.digest, computedDigest, MD5_DIGEST_LENGTH) != 0) {
return ResponseCode::VALUE_CORRUPTED;
}
}
}
const ssize_t maxValueLength = fileLength - offsetof(blobv3, value) - mBlob.info;
mBlob.length = ntohl(mBlob.length);
if (mBlob.length < 0 || mBlob.length > maxValueLength ||
mBlob.length + mBlob.info + AES_BLOCK_SIZE > static_cast<ssize_t>(sizeof(mBlob.value))) {
return ResponseCode::VALUE_CORRUPTED;
}
if (mBlob.info != 0 && mBlob.version < 3) {
// move info from after padding to after data
memmove(mBlob.value + mBlob.length, mBlob.value + maxValueLength, mBlob.info);
}
return ResponseCode::NO_ERROR;
}