Merge "Add encryption in incidentd." into qt-dev
diff --git a/cmds/incidentd/Android.bp b/cmds/incidentd/Android.bp
index 8f9a5f8..9c9b6c7 100644
--- a/cmds/incidentd/Android.bp
+++ b/cmds/incidentd/Android.bp
@@ -59,6 +59,12 @@
"libservices",
"libutils",
"libprotobuf-cpp-lite",
+ "libcrypto",
+ "libkeystore_aidl",
+ "libkeystore_binder",
+ "libkeystore_parcelables",
+ "android.hardware.keymaster@4.0",
+ "libkeymaster4support",
],
static_libs: [
@@ -111,6 +117,8 @@
"src/incidentd_util.cpp",
"src/proto_util.cpp",
"src/report_directory.cpp",
+ "src/cipher/IncidentKeyStore.cpp",
+ "src/cipher/ProtoEncryption.cpp",
"src/**/*.proto",
],
@@ -132,6 +140,12 @@
"libprotoutil",
"libservices",
"libutils",
+ "libcrypto",
+ "libkeystore_aidl",
+ "libkeystore_binder",
+ "libkeystore_parcelables",
+ "android.hardware.keymaster@4.0",
+ "libkeymaster4support",
],
target: {
diff --git a/cmds/incidentd/src/Privacy.cpp b/cmds/incidentd/src/Privacy.cpp
index 386303b..91f0dd3 100644
--- a/cmds/incidentd/src/Privacy.cpp
+++ b/cmds/incidentd/src/Privacy.cpp
@@ -37,6 +37,8 @@
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 fc8caae..b599c1c4 100644
--- a/cmds/incidentd/src/Privacy.h
+++ b/cmds/incidentd/src/Privacy.h
@@ -87,6 +87,9 @@
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 7126322..e8fa4f4 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 @@
*/
status_t writeData(int fd);
+ sp<ProtoReader> getData() { return mData; }
+
private:
/**
* The global set of field --> required privacy level mapping.
@@ -247,8 +252,47 @@
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 @@
// 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 @@
// 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 @@
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 @@
}
// 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 @@
// 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 76b2849..d426db9 100644
--- a/cmds/incidentd/src/PrivacyFilter.h
+++ b/cmds/incidentd/src/PrivacyFilter.h
@@ -82,8 +82,14 @@
* 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 218c1b2..322b972 100644
--- a/cmds/incidentd/src/Reporter.cpp
+++ b/cmds/incidentd/src/Reporter.cpp
@@ -447,7 +447,8 @@
}
});
- 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 0000000..ae0a920
--- /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 0000000..27611cd
--- /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 0000000..493796d
--- /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 0000000..5b72ca8
--- /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 0000000..5c7ed24
--- /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;
+}
diff --git a/cmds/incidentd/tests/IncidentKeyStore_test.cpp b/cmds/incidentd/tests/IncidentKeyStore_test.cpp
new file mode 100644
index 0000000..2250fda
--- /dev/null
+++ b/cmds/incidentd/tests/IncidentKeyStore_test.cpp
@@ -0,0 +1,66 @@
+/*
+ * 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 "cipher/IncidentKeyStore.h"
+
+#include <binder/ProcessState.h>
+#include <gtest/gtest.h>
+
+#include <fstream>
+
+using namespace android::os::incidentd;
+
+class IncidentKeyStoreTest : public ::testing::Test {
+protected:
+ std::unique_ptr<IncidentKeyStore> incidentKeyStore;
+ void SetUp() override {
+ android::ProcessState::self()->startThreadPool();
+ incidentKeyStore = std::make_unique<IncidentKeyStore>(
+ static_cast<keystore::KeystoreClient*>(new keystore::KeystoreClientImpl));
+ };
+ void TearDown() override { incidentKeyStore = nullptr; };
+};
+
+TEST_F(IncidentKeyStoreTest, test_encrypt_decrypt) {
+ std::string plaintext;
+ plaintext.resize(4 * 1024, 'a');
+
+ std::string encrypted;
+ EXPECT_TRUE(incidentKeyStore->encrypt(plaintext, 0, &encrypted));
+ std::string decrypted;
+ EXPECT_TRUE(incidentKeyStore->decrypt(encrypted, &decrypted));
+
+ EXPECT_FALSE(encrypted.empty());
+ EXPECT_EQ(plaintext, decrypted);
+}
+
+TEST_F(IncidentKeyStoreTest, test_encrypt_empty_hash) {
+ std::string hash = "";
+
+ std::string encrypted;
+ EXPECT_FALSE(incidentKeyStore->encrypt(hash, 0, &encrypted));
+
+ EXPECT_TRUE(encrypted.empty());
+}
+
+TEST_F(IncidentKeyStoreTest, test_decrypt_empty_hash) {
+ std::string hash = "";
+
+ std::string decrypted;
+ EXPECT_FALSE(incidentKeyStore->decrypt(hash, &decrypted));
+
+ EXPECT_TRUE(decrypted.empty());
+}
\ No newline at end of file
diff --git a/cmds/incidentd/tests/ProtoEncryption_test.cpp b/cmds/incidentd/tests/ProtoEncryption_test.cpp
new file mode 100644
index 0000000..6742e03
--- /dev/null
+++ b/cmds/incidentd/tests/ProtoEncryption_test.cpp
@@ -0,0 +1,85 @@
+/*
+ * 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 "cipher/ProtoEncryption.h"
+
+#include <android-base/file.h>
+#include <gtest/gtest.h>
+
+#include "FdBuffer.h"
+#include "android/util/ProtoFileReader.h"
+
+using namespace android::os::incidentd;
+using android::sp;
+using std::string;
+using ::testing::Test;
+
+const std::string kTestPath = GetExecutableDirectory();
+const std::string kTestDataPath = kTestPath + "/testdata/";
+
+TEST(ProtoEncryptionTest, test_encrypt_decrypt) {
+ const std::string plaintextFile = kTestDataPath + "plaintext.txt";
+ const std::string encryptedFile = kTestDataPath + "encrypted.txt";
+ size_t msg1Size = 20 * 1024;
+
+ // Create a file with plain text.
+ {
+ unique_fd fd(
+ open(plaintextFile.c_str(), O_WRONLY | O_CREAT | O_CLOEXEC, S_IRUSR | S_IWUSR));
+ ASSERT_NE(fd.get(), -1);
+ string content;
+ content.resize(msg1Size, 'a');
+ WriteFully(fd, content.data(), msg1Size);
+ }
+
+ // Read the plain text and encrypted
+ {
+ unique_fd readFd(open(plaintextFile.c_str(), O_RDONLY | O_CLOEXEC));
+ unique_fd encryptedFd(
+ open(encryptedFile.c_str(), O_WRONLY | O_CREAT | O_CLOEXEC, S_IRUSR | S_IWUSR));
+
+ ASSERT_NE(readFd.get(), -1);
+ ASSERT_NE(encryptedFd.get(), -1);
+
+ sp<ProtoFileReader> reader = new ProtoFileReader(readFd.get());
+ ProtoEncryptor encryptor(reader);
+ EXPECT_TRUE(encryptor.encrypt() > msg1Size);
+
+ encryptor.flush(encryptedFd.get());
+ }
+
+ // Read the encrypted file, and decrypt
+ unique_fd encryptedFd(open(encryptedFile.c_str(), O_RDONLY | O_CLOEXEC));
+ ASSERT_NE(encryptedFd.get(), -1);
+ FdBuffer output;
+ sp<ProtoFileReader> reader2 = new ProtoFileReader(encryptedFd.get());
+ ProtoDecryptor decryptor(reader2, reader2->size());
+ decryptor.decryptAndFlush(&output);
+
+ auto decryptedReader = output.data()->read();
+
+ // Check the content.
+ int count = 0;
+ while (decryptedReader->hasNext()) {
+ if (decryptedReader->next() == 'a') {
+ count++;
+ }
+ }
+
+ EXPECT_EQ(msg1Size, count);
+}
\ No newline at end of file
diff --git a/libs/protoutil/src/ProtoFileReader.cpp b/libs/protoutil/src/ProtoFileReader.cpp
index bbb1fe3..c7f1129 100644
--- a/libs/protoutil/src/ProtoFileReader.cpp
+++ b/libs/protoutil/src/ProtoFileReader.cpp
@@ -99,6 +99,7 @@
// Shouldn't get to here. Always call hasNext() before calling next().
return 0;
}
+ mPos++;
return mBuffer[mOffset++];
}
@@ -130,6 +131,7 @@
const size_t chunk =
mMaxOffset - mOffset > amt ? amt : mMaxOffset - mOffset;
mOffset += chunk;
+ mPos += chunk;
amt -= chunk;
}
}