213 lines
6.5 KiB
C++
213 lines
6.5 KiB
C++
/*
|
|
* 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;
|
|
}
|