diff options
author | Yao Chen <yaochen@google.com> | 2019-04-21 14:34:30 -0700 |
---|---|---|
committer | Yao Chen <yaochen@google.com> | 2019-04-23 15:20:38 -0700 |
commit | 43706b44705640aec6c3230c25502f6207d01c3d (patch) | |
tree | 70cff4df7ff27b2ac82ca5b2812f838740a39564 /cmds/incidentd/src | |
parent | 7feb1a1f7d0526dc0518be1ee5068ef51a48e074 (diff) |
Add encryption in incidentd.
+ Sections which require encryption will be encryted on disk.
+ When the sections are requested by clients (e.g., permission controller, report assignee),
incidentd will decrypte the data.
+ For efficiency, encryption is done ONLY for sections that require encryption.
+ Use Keystore API for key management.
Bug: 131084614
Test: incidentd_test
Change-Id: I84d6b86807ba5bbde1051e847b2df6e79e6b5be5
Diffstat (limited to 'cmds/incidentd/src')
-rw-r--r-- | cmds/incidentd/src/Privacy.cpp | 2 | ||||
-rw-r--r-- | cmds/incidentd/src/Privacy.h | 3 | ||||
-rw-r--r-- | cmds/incidentd/src/PrivacyFilter.cpp | 89 | ||||
-rw-r--r-- | cmds/incidentd/src/PrivacyFilter.h | 8 | ||||
-rw-r--r-- | cmds/incidentd/src/Reporter.cpp | 3 | ||||
-rw-r--r-- | cmds/incidentd/src/cipher/IncidentKeyStore.cpp | 87 | ||||
-rw-r--r-- | cmds/incidentd/src/cipher/IncidentKeyStore.h | 53 | ||||
-rw-r--r-- | cmds/incidentd/src/cipher/ProtoEncryption.cpp | 139 | ||||
-rw-r--r-- | cmds/incidentd/src/cipher/ProtoEncryption.h | 80 | ||||
-rw-r--r-- | cmds/incidentd/src/cipher/cipher_blocks.proto | 25 |
10 files changed, 466 insertions, 23 deletions
diff --git a/cmds/incidentd/src/Privacy.cpp b/cmds/incidentd/src/Privacy.cpp index 386303b038e7..91f0dd3a5c35 100644 --- a/cmds/incidentd/src/Privacy.cpp +++ b/cmds/incidentd/src/Privacy.cpp @@ -37,6 +37,8 @@ const Privacy* lookup(const Privacy* p, uint32_t fieldId) { return NULL; } +bool sectionEncryption(int section_id) { return section_id == 3025 /*restricted image section*/; } + static bool isAllowed(const uint8_t policy, const uint8_t check) { switch (check) { case PRIVACY_POLICY_LOCAL: diff --git a/cmds/incidentd/src/Privacy.h b/cmds/incidentd/src/Privacy.h index fc8caae7fc60..b599c1c4e344 100644 --- a/cmds/incidentd/src/Privacy.h +++ b/cmds/incidentd/src/Privacy.h @@ -87,6 +87,9 @@ private: uint8_t mPolicy; }; +// TODO: Add privacy flag in incident.proto and auto generate it inside Privacy. +bool sectionEncryption(int section_id); + } // namespace incidentd } // namespace os } // namespace android diff --git a/cmds/incidentd/src/PrivacyFilter.cpp b/cmds/incidentd/src/PrivacyFilter.cpp index 7126322575d5..e8fa4f4ee58e 100644 --- a/cmds/incidentd/src/PrivacyFilter.cpp +++ b/cmds/incidentd/src/PrivacyFilter.cpp @@ -16,15 +16,18 @@ #define DEBUG false #include "Log.h" -#include "incidentd_util.h" #include "PrivacyFilter.h" -#include "proto_util.h" #include <android-base/file.h> -#include <android/util/protobuf.h> #include <android/util/ProtoFileReader.h> +#include <android/util/protobuf.h> #include <log/log.h> +#include "cipher/IncidentKeyStore.h" +#include "cipher/ProtoEncryption.h" +#include "incidentd_util.h" +#include "proto_util.h" + namespace android { namespace os { namespace incidentd { @@ -141,6 +144,8 @@ public: */ status_t writeData(int fd); + sp<ProtoReader> getData() { return mData; } + private: /** * The global set of field --> required privacy level mapping. @@ -247,8 +252,47 @@ void PrivacyFilter::addFd(const sp<FilterFd>& output) { mOutputs.push_back(output); } -status_t PrivacyFilter::writeData(const FdBuffer& buffer, uint8_t bufferLevel, - size_t* maxSize) { +static void write_section_to_file(int sectionId, FieldStripper& fieldStripper, sp<FilterFd> output, + bool encryptIfNeeded) { + status_t err; + + if (sectionEncryption(sectionId) && encryptIfNeeded) { + ProtoEncryptor encryptor(fieldStripper.getData()); + size_t encryptedSize = encryptor.encrypt(); + + if (encryptedSize <= 0) { + output->onWriteError(BAD_VALUE); + return; + } + err = write_section_header(output->getFd(), sectionId, encryptedSize); + VLOG("Encrypted: write section header size %lu", (unsigned long)encryptedSize); + + encryptor.flush(output->getFd()); + + if (err != NO_ERROR) { + output->onWriteError(err); + return; + } + } else { + err = write_section_header(output->getFd(), sectionId, fieldStripper.dataSize()); + VLOG("No encryption: write section header size %lu", + (unsigned long)fieldStripper.dataSize()); + + if (err != NO_ERROR) { + output->onWriteError(err); + return; + } + + err = fieldStripper.writeData(output->getFd()); + if (err != NO_ERROR) { + output->onWriteError(err); + return; + } + } +} + +status_t PrivacyFilter::writeData(const FdBuffer& buffer, uint8_t bufferLevel, size_t* maxSize, + bool encryptIfNeeded) { status_t err; if (maxSize != NULL) { @@ -258,9 +302,9 @@ status_t PrivacyFilter::writeData(const FdBuffer& buffer, uint8_t bufferLevel, // Order the writes by privacy filter, with increasing levels of filtration,k // so we can do the filter once, and then write many times. sort(mOutputs.begin(), mOutputs.end(), - [](const sp<FilterFd>& a, const sp<FilterFd>& b) -> bool { - return a->getPrivacyPolicy() < b->getPrivacyPolicy(); - }); + [](const sp<FilterFd>& a, const sp<FilterFd>& b) -> bool { + return a->getPrivacyPolicy() < b->getPrivacyPolicy(); + }); uint8_t privacyPolicy = PRIVACY_POLICY_LOCAL; // a.k.a. no filtering FieldStripper fieldStripper(mRestrictions, buffer.data()->read(), bufferLevel); @@ -279,17 +323,7 @@ status_t PrivacyFilter::writeData(const FdBuffer& buffer, uint8_t bufferLevel, // Write the resultant buffer to the fd, along with the header. ssize_t dataSize = fieldStripper.dataSize(); if (dataSize > 0) { - err = write_section_header(output->getFd(), mSectionId, dataSize); - if (err != NO_ERROR) { - output->onWriteError(err); - continue; - } - - err = fieldStripper.writeData(output->getFd()); - if (err != NO_ERROR) { - output->onWriteError(err); - continue; - } + write_section_to_file(mSectionId, fieldStripper, output, encryptIfNeeded); } if (maxSize != NULL) { @@ -334,14 +368,25 @@ status_t filter_and_write_report(int to, int from, uint8_t bufferLevel, uint32_t fieldId = read_field_id(fieldTag); uint8_t wireType = read_wire_type(fieldTag); if (wireType == WIRE_TYPE_LENGTH_DELIMITED && args.containsSection(fieldId)) { + VLOG("Read section %d", fieldId); // We need this field, but we need to strip it to the level provided in args. PrivacyFilter filter(fieldId, get_privacy_of_section(fieldId)); filter.addFd(new ReadbackFilterFd(args.getPrivacyPolicy(), to)); // Read this section from the reader into an FdBuffer size_t sectionSize = reader->readRawVarint(); + FdBuffer sectionData; - err = sectionData.write(reader, sectionSize); + + // Write data to FdBuffer, if the section was encrypted, decrypt first. + if (sectionEncryption(fieldId)) { + VLOG("sectionSize %lu", (unsigned long)sectionSize); + ProtoDecryptor decryptor(reader, sectionSize); + err = decryptor.decryptAndFlush(§ionData); + } else { + err = sectionData.write(reader, sectionSize); + } + if (err != NO_ERROR) { ALOGW("filter_and_write_report FdBuffer.write failed (this shouldn't happen): %s", strerror(-err)); @@ -349,7 +394,8 @@ status_t filter_and_write_report(int to, int from, uint8_t bufferLevel, } // Do the filter and write. - err = filter.writeData(sectionData, bufferLevel, nullptr); + err = filter.writeData(sectionData, bufferLevel, nullptr, + false /* do not encrypt again*/); if (err != NO_ERROR) { ALOGW("filter_and_write_report filter.writeData had an error: %s", strerror(-err)); return err; @@ -358,6 +404,7 @@ status_t filter_and_write_report(int to, int from, uint8_t bufferLevel, // We don't need this field. Incident does not have any direct children // other than sections. So just skip them. write_field_or_skip(NULL, reader, fieldTag, true); + VLOG("Skip this.... section %d", fieldId); } } diff --git a/cmds/incidentd/src/PrivacyFilter.h b/cmds/incidentd/src/PrivacyFilter.h index 76b28498a0ac..d426db966a9a 100644 --- a/cmds/incidentd/src/PrivacyFilter.h +++ b/cmds/incidentd/src/PrivacyFilter.h @@ -82,8 +82,14 @@ public: * was written (i.e. after filtering). * * The buffer is assumed to have already been filtered to bufferLevel. + * + * This function can be called when persisting data to disk or when sending + * data to client. In the former case, we need to encrypt the data when that + * section requires encryption. In the latter case, we shouldn't send the + * unencrypted data to client. */ - status_t writeData(const FdBuffer& buffer, uint8_t bufferLevel, size_t* maxSize); + status_t writeData(const FdBuffer& buffer, uint8_t bufferLevel, size_t* maxSize, + bool encryptIfNeeded); private: int mSectionId; diff --git a/cmds/incidentd/src/Reporter.cpp b/cmds/incidentd/src/Reporter.cpp index 218c1b27fdcc..322b97293a26 100644 --- a/cmds/incidentd/src/Reporter.cpp +++ b/cmds/incidentd/src/Reporter.cpp @@ -447,7 +447,8 @@ status_t ReportWriter::writeSection(const FdBuffer& buffer) { } }); - return filter.writeData(buffer, PRIVACY_POLICY_LOCAL, &mMaxSectionDataFilteredSize); + return filter.writeData(buffer, PRIVACY_POLICY_LOCAL, &mMaxSectionDataFilteredSize, + true /*encrypt if needed*/); } diff --git a/cmds/incidentd/src/cipher/IncidentKeyStore.cpp b/cmds/incidentd/src/cipher/IncidentKeyStore.cpp new file mode 100644 index 000000000000..ae0a92094d0b --- /dev/null +++ b/cmds/incidentd/src/cipher/IncidentKeyStore.cpp @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2019 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. + */ + +#include "Log.h" + +#include "IncidentKeyStore.h" + +#include <sys/stat.h> + +static constexpr size_t AES_KEY_BYTES = 32; +static constexpr size_t GCM_MAC_BYTES = 16; +constexpr char kKeyname[] = "IncidentKey"; + +namespace android { +namespace os { +namespace incidentd { + +using namespace keystore; +using std::string; + +IncidentKeyStore& IncidentKeyStore::getInstance() { + static IncidentKeyStore sInstance(new keystore::KeystoreClientImpl); + return sInstance; +} + +bool IncidentKeyStore::encrypt(const string& data, int32_t flags, string* output) { + std::lock_guard<std::mutex> lock(mMutex); + if (data.empty()) { + ALOGW("IncidentKeyStore: Encrypt empty data?!"); + return false; + } + if (!mClient->doesKeyExist(kKeyname)) { + auto gen_result = generateKeyLocked(kKeyname, 0); + if (!gen_result.isOk()) { + ALOGE("IncidentKeyStore: Key generate failed."); + return false; + } + } + if (!mClient->encryptWithAuthentication(kKeyname, data, flags, output)) { + ALOGE("IncidentKeyStore: Encryption failed."); + return false; + } + return true; +} + +bool IncidentKeyStore::decrypt(const std::string& input, string* output) { + std::lock_guard<std::mutex> lock(mMutex); + if (input.empty()) { + ALOGE("IncidentKeyStore: Decrypt empty input?"); + return false; + } + if (!mClient->decryptWithAuthentication(kKeyname, input, output)) { + ALOGE("IncidentKeyStore: Decryption failed."); + return false; + } + return true; +} + +KeyStoreNativeReturnCode IncidentKeyStore::generateKeyLocked(const std::string& name, + int32_t flags) { + auto paramBuilder = AuthorizationSetBuilder() + .AesEncryptionKey(AES_KEY_BYTES * 8) + .GcmModeMinMacLen(GCM_MAC_BYTES * 8) + .Authorization(TAG_NO_AUTH_REQUIRED); + + AuthorizationSet hardware_enforced_characteristics; + AuthorizationSet software_enforced_characteristics; + return mClient->generateKey(name, paramBuilder, flags, &hardware_enforced_characteristics, + &software_enforced_characteristics); +} + +} // namespace incidentd +} // namespace os +} // namespace android diff --git a/cmds/incidentd/src/cipher/IncidentKeyStore.h b/cmds/incidentd/src/cipher/IncidentKeyStore.h new file mode 100644 index 000000000000..27611cd7faad --- /dev/null +++ b/cmds/incidentd/src/cipher/IncidentKeyStore.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2019 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. + */ +#pragma once + +#include <keystore/keystore_client_impl.h> + +namespace android { +namespace os { +namespace incidentd { + +class IncidentKeyStore { +public: + static IncidentKeyStore& getInstance(); + + IncidentKeyStore(keystore::KeystoreClient* client) : mClient(client) {} + + /** + * Encrypt the plainText and output the encrypted message. + * + * Returns true on success and false otherwise. + * If the key has not been created yet, it will generate the key in KeyMaster. + */ + bool encrypt(const std::string& plainText, int32_t flags, std::string* output); + + /** + * Decrypt and output the decrypted message. + * + * Returns true on success and false otherwise. + */ + bool decrypt(const std::string& encryptedData, std::string* output); + +private: + std::unique_ptr<keystore::KeystoreClient> mClient; + std::mutex mMutex; + keystore::KeyStoreNativeReturnCode generateKeyLocked(const std::string& name, int32_t flags); +}; + +} // namespace incidentd +} // namespace os +} // namespace android diff --git a/cmds/incidentd/src/cipher/ProtoEncryption.cpp b/cmds/incidentd/src/cipher/ProtoEncryption.cpp new file mode 100644 index 000000000000..493796d2af4d --- /dev/null +++ b/cmds/incidentd/src/cipher/ProtoEncryption.cpp @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2019 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 DEBUG true // STOPSHIP if true +#include "Log.h" + +#include "ProtoEncryption.h" + +#include <android/util/protobuf.h> + +#include "IncidentKeyStore.h" + +namespace android { +namespace os { +namespace incidentd { + +using android::util::FIELD_COUNT_REPEATED; +using android::util::FIELD_TYPE_STRING; +using android::util::ProtoOutputStream; +using android::util::ProtoReader; +using std::string; + +static const int FIELD_ID_BLOCK = 1; + +size_t ProtoEncryptor::encrypt() { + string block; + int i = 0; + // Read at most sBlockSize at a time and encrypt. + while (mReader->readBuffer() != NULL) { + size_t readBytes = + mReader->currentToRead() > sBlockSize ? sBlockSize : mReader->currentToRead(); + block.resize(readBytes); + std::memcpy(block.data(), mReader->readBuffer(), readBytes); + + string encrypted; + if (IncidentKeyStore::getInstance().encrypt(block, 0, &encrypted)) { + mOutputStream.write(FIELD_TYPE_STRING | FIELD_ID_BLOCK | FIELD_COUNT_REPEATED, + encrypted); + VLOG("Block %d Encryption: original %lld now %lld", i++, (long long)readBytes, + (long long)encrypted.length()); + mReader->move(readBytes); + } else { + return 0; + } + } + return mOutputStream.size(); +} + +status_t ProtoEncryptor::flush(int fd) { + if (!mOutputStream.flush(fd)) { + return BAD_VALUE; + } + return NO_ERROR; +} + +status_t ProtoDecryptor::readOneBlock(string* output) { + if (!mReader->hasNext()) { + return NO_ERROR; + } + uint64_t fieldTag = mReader->readRawVarint(); + uint32_t fieldId = read_field_id(fieldTag); + uint8_t wireType = read_wire_type(fieldTag); + if (wireType == WIRE_TYPE_LENGTH_DELIMITED) { + // Read this section from the reader into an FdBuffer + size_t sectionSize = mReader->readRawVarint(); + output->resize(sectionSize); + size_t pos = 0; + while (pos < sectionSize && mReader->readBuffer() != NULL) { + size_t toRead = (sectionSize - pos) > mReader->currentToRead() + ? mReader->currentToRead() + : (sectionSize - pos); + std::memcpy(&((output->data())[pos]), mReader->readBuffer(), toRead); + pos += toRead; + mReader->move(toRead); + } + if (pos != sectionSize) { + return BAD_VALUE; + ALOGE("Failed to read one block"); + } + } else { + return BAD_VALUE; + } + return NO_ERROR; +} + +status_t ProtoDecryptor::decryptAndFlush(FdBuffer* out) { + size_t mStartBytes = mReader->bytesRead(); + size_t bytesRead = 0; + int i = 0; + status_t err = NO_ERROR; + // Let's read until we read mTotalSize. If any error occurs before that, make sure to move the + // read pointer so the caller can continue to read the following sections. + while (bytesRead < mTotalSize) { + string block; + err = readOneBlock(&block); + bytesRead = mReader->bytesRead() - mStartBytes; + + if (err != NO_ERROR) { + break; + } + + if (block.length() == 0) { + VLOG("Done reading all blocks"); + break; + } + + string decryptedBlock; + if ((IncidentKeyStore::getInstance()).decrypt(block, &decryptedBlock)) { + VLOG("Block %d Original Size %lu Decrypted size %lu", i++, + (unsigned long)block.length(), (unsigned long)decryptedBlock.length()); + out->write(reinterpret_cast<uint8_t*>(decryptedBlock.data()), decryptedBlock.length()); + } else { + err = BAD_VALUE; + break; + } + } + + if (bytesRead < mTotalSize) { + mReader->move(mTotalSize - bytesRead); + } + return err; +} + +} // namespace incidentd +} // namespace os +} // namespace android diff --git a/cmds/incidentd/src/cipher/ProtoEncryption.h b/cmds/incidentd/src/cipher/ProtoEncryption.h new file mode 100644 index 000000000000..5b72ca88ec64 --- /dev/null +++ b/cmds/incidentd/src/cipher/ProtoEncryption.h @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2019 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. + */ +#pragma once + +#include <android/util/ProtoOutputStream.h> +#include <android/util/ProtoReader.h> +#include <frameworks/base/cmds/incidentd/src/cipher/cipher_blocks.pb.h> + +#include "FdBuffer.h" + +namespace android { +namespace os { +namespace incidentd { + +// PlainText IncidentReport format +// [section1_header(id, size, type)][section1_data] ... + +// Let's say section1 needs encryption +// After encryption, it becomes +// [section1_header(id, encrypted_size, type)][[cipher_block][cipher_block][cipher_block]..] + +// When clients read the report, it's decrypted, and written in its original format + +/** + * Takes a ProtoReader, encrypts its whole content -- which is one section, and flush to + * a file descriptor. + * The underlying encryption is done using Keystore binder APIs. We encrypt the data + * in blocks, and write to the file in android.os.incidentd.CipherBlocks format. + */ +class ProtoEncryptor { +public: + ProtoEncryptor(const sp<android::util::ProtoReader>& reader) : mReader(reader){}; + + // Encrypt the data from ProtoReader, and store in CipherBlocks format. + // return the size of CipherBlocks. + size_t encrypt(); + + status_t flush(int fd); + +private: + static const size_t sBlockSize = 8 * 1024; + const sp<android::util::ProtoReader> mReader; + android::util::ProtoOutputStream mOutputStream; +}; + +// Read data from ProtoReader, which is in CipherBlocks proto format. Parse and decrypt +// block by block. +class ProtoDecryptor { +public: + ProtoDecryptor(const sp<android::util::ProtoReader>& reader, size_t size) + : mReader(reader), mTotalSize(size){}; + status_t decryptAndFlush(FdBuffer* out); + +private: + const sp<android::util::ProtoReader> mReader; + + // Total size in bytes we should read from ProtoReader. + const size_t mTotalSize; + + // Read one cipher block from ProtoReader, instead of reading the whole content + // and parse to CipherBlocks which could be huge. + status_t readOneBlock(std::string* output); +}; + +} // namespace incidentd +} // namespace os +} // namespace android diff --git a/cmds/incidentd/src/cipher/cipher_blocks.proto b/cmds/incidentd/src/cipher/cipher_blocks.proto new file mode 100644 index 000000000000..5c7ed242c7a5 --- /dev/null +++ b/cmds/incidentd/src/cipher/cipher_blocks.proto @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2019 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. + */ + +syntax = "proto2"; + +package android.os.incidentd; + +// This proto is never instantiated anywhere. It only exists to keep a record of the format of the +// encrypted data on disk. +message CipherBlocks { + repeated string blocks = 1; +} |