| /* |
| * 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; |
| } |