Merge "Add encryption in incidentd." into qt-dev
am: a35c2f1224
Change-Id: Ifb13a0ea562cdf1d5ada037c38b21094aaf7ad59
This commit is contained in:
@@ -59,6 +59,12 @@ cc_binary {
|
|||||||
"libservices",
|
"libservices",
|
||||||
"libutils",
|
"libutils",
|
||||||
"libprotobuf-cpp-lite",
|
"libprotobuf-cpp-lite",
|
||||||
|
"libcrypto",
|
||||||
|
"libkeystore_aidl",
|
||||||
|
"libkeystore_binder",
|
||||||
|
"libkeystore_parcelables",
|
||||||
|
"android.hardware.keymaster@4.0",
|
||||||
|
"libkeymaster4support",
|
||||||
],
|
],
|
||||||
|
|
||||||
static_libs: [
|
static_libs: [
|
||||||
@@ -111,6 +117,8 @@ cc_test {
|
|||||||
"src/incidentd_util.cpp",
|
"src/incidentd_util.cpp",
|
||||||
"src/proto_util.cpp",
|
"src/proto_util.cpp",
|
||||||
"src/report_directory.cpp",
|
"src/report_directory.cpp",
|
||||||
|
"src/cipher/IncidentKeyStore.cpp",
|
||||||
|
"src/cipher/ProtoEncryption.cpp",
|
||||||
"src/**/*.proto",
|
"src/**/*.proto",
|
||||||
],
|
],
|
||||||
|
|
||||||
@@ -132,6 +140,12 @@ cc_test {
|
|||||||
"libprotoutil",
|
"libprotoutil",
|
||||||
"libservices",
|
"libservices",
|
||||||
"libutils",
|
"libutils",
|
||||||
|
"libcrypto",
|
||||||
|
"libkeystore_aidl",
|
||||||
|
"libkeystore_binder",
|
||||||
|
"libkeystore_parcelables",
|
||||||
|
"android.hardware.keymaster@4.0",
|
||||||
|
"libkeymaster4support",
|
||||||
],
|
],
|
||||||
|
|
||||||
target: {
|
target: {
|
||||||
|
|||||||
@@ -37,6 +37,8 @@ const Privacy* lookup(const Privacy* p, uint32_t fieldId) {
|
|||||||
return NULL;
|
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) {
|
static bool isAllowed(const uint8_t policy, const uint8_t check) {
|
||||||
switch (check) {
|
switch (check) {
|
||||||
case PRIVACY_POLICY_LOCAL:
|
case PRIVACY_POLICY_LOCAL:
|
||||||
|
|||||||
@@ -87,6 +87,9 @@ private:
|
|||||||
uint8_t mPolicy;
|
uint8_t mPolicy;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// TODO: Add privacy flag in incident.proto and auto generate it inside Privacy.
|
||||||
|
bool sectionEncryption(int section_id);
|
||||||
|
|
||||||
} // namespace incidentd
|
} // namespace incidentd
|
||||||
} // namespace os
|
} // namespace os
|
||||||
} // namespace android
|
} // namespace android
|
||||||
|
|||||||
@@ -16,15 +16,18 @@
|
|||||||
#define DEBUG false
|
#define DEBUG false
|
||||||
#include "Log.h"
|
#include "Log.h"
|
||||||
|
|
||||||
#include "incidentd_util.h"
|
|
||||||
#include "PrivacyFilter.h"
|
#include "PrivacyFilter.h"
|
||||||
#include "proto_util.h"
|
|
||||||
|
|
||||||
#include <android-base/file.h>
|
#include <android-base/file.h>
|
||||||
#include <android/util/protobuf.h>
|
|
||||||
#include <android/util/ProtoFileReader.h>
|
#include <android/util/ProtoFileReader.h>
|
||||||
|
#include <android/util/protobuf.h>
|
||||||
#include <log/log.h>
|
#include <log/log.h>
|
||||||
|
|
||||||
|
#include "cipher/IncidentKeyStore.h"
|
||||||
|
#include "cipher/ProtoEncryption.h"
|
||||||
|
#include "incidentd_util.h"
|
||||||
|
#include "proto_util.h"
|
||||||
|
|
||||||
namespace android {
|
namespace android {
|
||||||
namespace os {
|
namespace os {
|
||||||
namespace incidentd {
|
namespace incidentd {
|
||||||
@@ -141,6 +144,8 @@ public:
|
|||||||
*/
|
*/
|
||||||
status_t writeData(int fd);
|
status_t writeData(int fd);
|
||||||
|
|
||||||
|
sp<ProtoReader> getData() { return mData; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/**
|
/**
|
||||||
* The global set of field --> required privacy level mapping.
|
* The global set of field --> required privacy level mapping.
|
||||||
@@ -247,8 +252,47 @@ void PrivacyFilter::addFd(const sp<FilterFd>& output) {
|
|||||||
mOutputs.push_back(output);
|
mOutputs.push_back(output);
|
||||||
}
|
}
|
||||||
|
|
||||||
status_t PrivacyFilter::writeData(const FdBuffer& buffer, uint8_t bufferLevel,
|
static void write_section_to_file(int sectionId, FieldStripper& fieldStripper, sp<FilterFd> output,
|
||||||
size_t* maxSize) {
|
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;
|
status_t err;
|
||||||
|
|
||||||
if (maxSize != NULL) {
|
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
|
// Order the writes by privacy filter, with increasing levels of filtration,k
|
||||||
// so we can do the filter once, and then write many times.
|
// so we can do the filter once, and then write many times.
|
||||||
sort(mOutputs.begin(), mOutputs.end(),
|
sort(mOutputs.begin(), mOutputs.end(),
|
||||||
[](const sp<FilterFd>& a, const sp<FilterFd>& b) -> bool {
|
[](const sp<FilterFd>& a, const sp<FilterFd>& b) -> bool {
|
||||||
return a->getPrivacyPolicy() < b->getPrivacyPolicy();
|
return a->getPrivacyPolicy() < b->getPrivacyPolicy();
|
||||||
});
|
});
|
||||||
|
|
||||||
uint8_t privacyPolicy = PRIVACY_POLICY_LOCAL; // a.k.a. no filtering
|
uint8_t privacyPolicy = PRIVACY_POLICY_LOCAL; // a.k.a. no filtering
|
||||||
FieldStripper fieldStripper(mRestrictions, buffer.data()->read(), bufferLevel);
|
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.
|
// Write the resultant buffer to the fd, along with the header.
|
||||||
ssize_t dataSize = fieldStripper.dataSize();
|
ssize_t dataSize = fieldStripper.dataSize();
|
||||||
if (dataSize > 0) {
|
if (dataSize > 0) {
|
||||||
err = write_section_header(output->getFd(), mSectionId, dataSize);
|
write_section_to_file(mSectionId, fieldStripper, output, encryptIfNeeded);
|
||||||
if (err != NO_ERROR) {
|
|
||||||
output->onWriteError(err);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
err = fieldStripper.writeData(output->getFd());
|
|
||||||
if (err != NO_ERROR) {
|
|
||||||
output->onWriteError(err);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (maxSize != NULL) {
|
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);
|
uint32_t fieldId = read_field_id(fieldTag);
|
||||||
uint8_t wireType = read_wire_type(fieldTag);
|
uint8_t wireType = read_wire_type(fieldTag);
|
||||||
if (wireType == WIRE_TYPE_LENGTH_DELIMITED && args.containsSection(fieldId)) {
|
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.
|
// We need this field, but we need to strip it to the level provided in args.
|
||||||
PrivacyFilter filter(fieldId, get_privacy_of_section(fieldId));
|
PrivacyFilter filter(fieldId, get_privacy_of_section(fieldId));
|
||||||
filter.addFd(new ReadbackFilterFd(args.getPrivacyPolicy(), to));
|
filter.addFd(new ReadbackFilterFd(args.getPrivacyPolicy(), to));
|
||||||
|
|
||||||
// Read this section from the reader into an FdBuffer
|
// Read this section from the reader into an FdBuffer
|
||||||
size_t sectionSize = reader->readRawVarint();
|
size_t sectionSize = reader->readRawVarint();
|
||||||
|
|
||||||
FdBuffer sectionData;
|
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) {
|
if (err != NO_ERROR) {
|
||||||
ALOGW("filter_and_write_report FdBuffer.write failed (this shouldn't happen): %s",
|
ALOGW("filter_and_write_report FdBuffer.write failed (this shouldn't happen): %s",
|
||||||
strerror(-err));
|
strerror(-err));
|
||||||
@@ -349,7 +394,8 @@ status_t filter_and_write_report(int to, int from, uint8_t bufferLevel,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Do the filter and write.
|
// 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) {
|
if (err != NO_ERROR) {
|
||||||
ALOGW("filter_and_write_report filter.writeData had an error: %s", strerror(-err));
|
ALOGW("filter_and_write_report filter.writeData had an error: %s", strerror(-err));
|
||||||
return 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
|
// We don't need this field. Incident does not have any direct children
|
||||||
// other than sections. So just skip them.
|
// other than sections. So just skip them.
|
||||||
write_field_or_skip(NULL, reader, fieldTag, true);
|
write_field_or_skip(NULL, reader, fieldTag, true);
|
||||||
|
VLOG("Skip this.... section %d", fieldId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -82,8 +82,14 @@ public:
|
|||||||
* was written (i.e. after filtering).
|
* was written (i.e. after filtering).
|
||||||
*
|
*
|
||||||
* The buffer is assumed to have already been filtered to bufferLevel.
|
* 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:
|
private:
|
||||||
int mSectionId;
|
int mSectionId;
|
||||||
|
|||||||
@@ -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*/);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
87
cmds/incidentd/src/cipher/IncidentKeyStore.cpp
Normal file
87
cmds/incidentd/src/cipher/IncidentKeyStore.cpp
Normal file
@@ -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
|
||||||
53
cmds/incidentd/src/cipher/IncidentKeyStore.h
Normal file
53
cmds/incidentd/src/cipher/IncidentKeyStore.h
Normal file
@@ -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
|
||||||
139
cmds/incidentd/src/cipher/ProtoEncryption.cpp
Normal file
139
cmds/incidentd/src/cipher/ProtoEncryption.cpp
Normal file
@@ -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
|
||||||
80
cmds/incidentd/src/cipher/ProtoEncryption.h
Normal file
80
cmds/incidentd/src/cipher/ProtoEncryption.h
Normal file
@@ -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
|
||||||
25
cmds/incidentd/src/cipher/cipher_blocks.proto
Normal file
25
cmds/incidentd/src/cipher/cipher_blocks.proto
Normal file
@@ -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;
|
||||||
|
}
|
||||||
66
cmds/incidentd/tests/IncidentKeyStore_test.cpp
Normal file
66
cmds/incidentd/tests/IncidentKeyStore_test.cpp
Normal file
@@ -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());
|
||||||
|
}
|
||||||
85
cmds/incidentd/tests/ProtoEncryption_test.cpp
Normal file
85
cmds/incidentd/tests/ProtoEncryption_test.cpp
Normal file
@@ -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);
|
||||||
|
}
|
||||||
@@ -99,6 +99,7 @@ ProtoFileReader::next()
|
|||||||
// Shouldn't get to here. Always call hasNext() before calling next().
|
// Shouldn't get to here. Always call hasNext() before calling next().
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
mPos++;
|
||||||
return mBuffer[mOffset++];
|
return mBuffer[mOffset++];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,6 +131,7 @@ ProtoFileReader::move(size_t amt)
|
|||||||
const size_t chunk =
|
const size_t chunk =
|
||||||
mMaxOffset - mOffset > amt ? amt : mMaxOffset - mOffset;
|
mMaxOffset - mOffset > amt ? amt : mMaxOffset - mOffset;
|
||||||
mOffset += chunk;
|
mOffset += chunk;
|
||||||
|
mPos += chunk;
|
||||||
amt -= chunk;
|
amt -= chunk;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user