Commit 2cb92bbf authored by Leonid Baraz's avatar Leonid Baraz Committed by Commit Bot

Declarations for go/chrome-reporting-encryption.

Fake implementation and unittests are provided, real implementation will
follow.

Bug: b:153649905
Change-Id: I00972b8a5e15b98ff21f6cc70ab4a6c31e6d9848
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2302881
Commit-Queue: Leonid Baraz <lbaraz@chromium.org>
Reviewed-by: default avatarZach Trudo <zatrudo@google.com>
Cr-Commit-Position: refs/heads/master@{#797055}
parent 52148d1a
......@@ -1160,8 +1160,16 @@ static_library("browser") {
"policy/homepage_location_policy_handler.h",
"policy/javascript_policy_handler.cc",
"policy/javascript_policy_handler.h",
"policy/messaging_layer/encryption/decryption.cc",
"policy/messaging_layer/encryption/decryption.h",
"policy/messaging_layer/encryption/encryption.cc",
"policy/messaging_layer/encryption/encryption.h",
"policy/messaging_layer/encryption/encryption_module.cc",
"policy/messaging_layer/encryption/encryption_module.h",
"policy/messaging_layer/encryption/fake_decryption.cc",
"policy/messaging_layer/encryption/fake_decryption.h",
"policy/messaging_layer/encryption/fake_encryption.cc",
"policy/messaging_layer/encryption/fake_encryption.h",
"policy/messaging_layer/public/report_client.cc",
"policy/messaging_layer/public/report_client.h",
"policy/messaging_layer/public/report_queue.cc",
......
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <string>
#include "base/hash/hash.h"
#include "base/strings/strcat.h"
#include "base/task/post_task.h"
#include "base/task_runner.h"
#include "chrome/browser/policy/messaging_layer/encryption/decryption.h"
namespace reporting {
DecryptorBase::Handle::Handle(scoped_refptr<DecryptorBase> decryptor)
: decryptor_(decryptor) {}
DecryptorBase::Handle::~Handle() = default;
DecryptorBase::DecryptorBase()
: keys_sequenced_task_runner_(base::ThreadPool::CreateSequencedTaskRunner(
{base::TaskPriority::BEST_EFFORT, base::MayBlock()})) {
DETACH_FROM_SEQUENCE(keys_sequence_checker_);
}
DecryptorBase::~DecryptorBase() = default;
void DecryptorBase::RecordKeyPair(base::StringPiece private_key,
base::StringPiece public_key,
base::OnceCallback<void(Status)> cb) {
// Schedule key recording on the sequenced task runner.
keys_sequenced_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(
[](std::string public_key, KeyInfo key_info,
base::OnceCallback<void(Status)> cb,
scoped_refptr<DecryptorBase> decryptor) {
DCHECK_CALLED_ON_VALID_SEQUENCE(decryptor->keys_sequence_checker_);
Status result;
if (!decryptor->keys_
.emplace(base::PersistentHash(public_key.data(),
public_key.size()),
key_info)
.second) {
result = Status(error::ALREADY_EXISTS,
base::StrCat({"Public key='", public_key,
"' already recorded"}));
}
// Schedule response on a generic thread pool.
base::ThreadPool::PostTask(
FROM_HERE,
base::BindOnce([](base::OnceCallback<void(Status)> cb,
Status result) { std::move(cb).Run(result); },
std::move(cb), result));
},
std::string(public_key),
KeyInfo{.private_key = std::string(private_key),
.time_stamp = base::Time::Now()},
std::move(cb), base::WrapRefCounted(this)));
}
void DecryptorBase::RetrieveMatchingPrivateKey(
uint32_t public_key_id,
base::OnceCallback<void(StatusOr<std::string>)> cb) {
// Schedule key retrieval on the sequenced task runner.
keys_sequenced_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(
[](uint32_t public_key_id,
base::OnceCallback<void(StatusOr<std::string>)> cb,
scoped_refptr<DecryptorBase> decryptor) {
DCHECK_CALLED_ON_VALID_SEQUENCE(decryptor->keys_sequence_checker_);
auto key_info_it = decryptor->keys_.find(public_key_id);
// Schedule response on a generic thread pool.
base::ThreadPool::PostTask(
FROM_HERE,
base::BindOnce(
[](base::OnceCallback<void(StatusOr<std::string>)> cb,
StatusOr<std::string> result) {
std::move(cb).Run(result);
},
std::move(cb),
key_info_it == decryptor->keys_.end()
? StatusOr<std::string>(Status(
error::NOT_FOUND, "Matching key not found"))
: key_info_it->second.private_key));
},
public_key_id, std::move(cb), base::WrapRefCounted(this)));
}
} // namespace reporting
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_POLICY_MESSAGING_LAYER_ENCRYPTION_DECRYPTION_H_
#define CHROME_BROWSER_POLICY_MESSAGING_LAYER_ENCRYPTION_DECRYPTION_H_
#include <string>
#include "base/callback.h"
#include "base/containers/flat_map.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_refptr.h"
#include "base/strings/string_piece.h"
#include "base/threading/thread.h"
#include "base/threading/thread_task_runner_handle.h"
#include "chrome/browser/policy/messaging_layer/util/status.h"
#include "chrome/browser/policy/messaging_layer/util/statusor.h"
namespace reporting {
// Interface to the encryption.
// Instantiated by an implementation-specific factory:
// StatusOr<scoped_refptr<DecryptorBase>> Create(
// implementation-specific parameters);
// The implementation class should never be used directly by the server code.
// Note: Production implementation should be written or enclosed in Java code
// for the server to use.
class DecryptorBase : public base::RefCountedThreadSafe<DecryptorBase> {
public:
// Decryption record handle, which is created by |OpenRecord| and can accept
// pieces of data to be decrypted as one record by calling |AddToRecord|
// multiple times. Resulting decrypted record is available once |CloseRecord|
// is called.
class Handle {
public:
// Adds piece of data to the record.
virtual void AddToRecord(base::StringPiece data,
base::OnceCallback<void(Status)> cb) = 0;
// Closes and attempts to decrypt the record. Hands over the decrypted data
// to be processed by the server (or Status if unsuccessful). Accesses key
// store to attempt all private keys that are considered to be valid,
// starting with the one that matches the hash. Self-destructs after the
// callback.
virtual void CloseRecord(
base::OnceCallback<void(StatusOr<base::StringPiece>)> cb) = 0;
protected:
explicit Handle(scoped_refptr<DecryptorBase> decryptor);
// Destructor is non-public, because the object can only self-destruct by
// |CloseRecord|.
virtual ~Handle();
DecryptorBase* decryptor() const { return decryptor_.get(); }
private:
scoped_refptr<DecryptorBase> decryptor_;
};
// Factory method creates new record to collect data and decrypt them with the
// given encrypted key. Hands the handle raw pointer over to the callback, or
// error status (e.g., “decryption is not enabled yet”)
virtual void OpenRecord(base::StringPiece encrypted_key,
base::OnceCallback<void(StatusOr<Handle*>)> cb) = 0;
// Decrypts symmetric key with asymmetric private key and returns unencrypted
// key or error status (e.g., “decryption is not enabled yet”)
virtual StatusOr<std::string> DecryptKey(base::StringPiece public_key,
base::StringPiece encrypted_key) = 0;
// Records a key pair (store only private key).
// Executes on a sequenced thread, returns with callback.
void RecordKeyPair(base::StringPiece private_key,
base::StringPiece public_key,
base::OnceCallback<void(Status)> cb);
// Retrieves private key matching the public key hash.
// Executes on a sequenced thread, returns with callback.
void RetrieveMatchingPrivateKey(
uint32_t public_key_id,
base::OnceCallback<void(StatusOr<std::string>)> cb);
protected:
DecryptorBase();
virtual ~DecryptorBase();
private:
friend base::RefCountedThreadSafe<DecryptorBase>;
// Map of hash(public_key)->{public key, private key, time stamp}
// Private key is located by the hash of a public key, sent together with the
// encrypted record. Keys older than pre-defined threshold are discarded.
struct KeyInfo {
std::string private_key;
base::Time time_stamp;
};
base::flat_map<uint32_t, KeyInfo> keys_;
// Sequential task runner for all keys_ activities: recording, lookup, purge.
scoped_refptr<base::SequencedTaskRunner> keys_sequenced_task_runner_;
SEQUENCE_CHECKER(keys_sequence_checker_);
};
} // namespace reporting
#endif // CHROME_BROWSER_POLICY_MESSAGING_LAYER_ENCRYPTION_DECRYPTION_H_
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <string>
#include <utility>
#include "base/hash/hash.h"
#include "base/task/post_task.h"
#include "base/task_runner.h"
#include "chrome/browser/policy/messaging_layer/encryption/encryption.h"
namespace reporting {
EncryptorBase::Handle::Handle(scoped_refptr<EncryptorBase> encryptor)
: encryptor_(encryptor) {}
EncryptorBase::Handle::~Handle() = default;
EncryptorBase::EncryptorBase()
: asymmetric_key_sequenced_task_runner_(
base::ThreadPool::CreateSequencedTaskRunner(
{base::TaskPriority::BEST_EFFORT, base::MayBlock()})) {
DETACH_FROM_SEQUENCE(asymmetric_key_sequence_checker_);
}
EncryptorBase::~EncryptorBase() = default;
void EncryptorBase::UpdateAsymmetricKey(
base::StringPiece new_key,
base::OnceCallback<void(Status)> response_cb) {
if (new_key.empty()) {
std::move(response_cb)
.Run(Status(error::INVALID_ARGUMENT, "Provided key is empty"));
return;
}
// Schedule key update on the sequenced task runner.
asymmetric_key_sequenced_task_runner_->PostTask(
FROM_HERE, base::BindOnce(
[](base::StringPiece new_key,
scoped_refptr<EncryptorBase> encryptor) {
encryptor->asymmetric_key_ = std::string(new_key);
},
new_key, base::WrapRefCounted(this)));
// Response OK not waiting for the update.
std::move(response_cb).Run(Status::StatusOK());
}
void EncryptorBase::RetrieveAsymmetricKey(
base::OnceCallback<void(StatusOr<std::string>)> cb) {
// Schedule key retrueval on the sequenced task runner.
asymmetric_key_sequenced_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(
[](base::OnceCallback<void(StatusOr<std::string>)> cb,
scoped_refptr<EncryptorBase> encryptor) {
DCHECK_CALLED_ON_VALID_SEQUENCE(
encryptor->asymmetric_key_sequence_checker_);
StatusOr<std::string> response;
// Schedule response on regular thread pool.
base::ThreadPool::PostTask(
FROM_HERE,
base::BindOnce(
[](base::OnceCallback<void(StatusOr<std::string>)> cb,
StatusOr<std::string> response) {
std::move(cb).Run(response);
},
std::move(cb),
!encryptor->asymmetric_key_.has_value()
? StatusOr<std::string>(Status(
error::NOT_FOUND, "Asymmetric key not set"))
: encryptor->asymmetric_key_.value()));
},
std::move(cb), base::WrapRefCounted(this)));
}
void EncryptorBase::EncryptKey(
base::StringPiece symmetric_key,
base::OnceCallback<void(StatusOr<std::pair<uint32_t, std::string>>)> cb) {
RetrieveAsymmetricKey(base::BindOnce(
[](base::StringPiece symmetric_key,
scoped_refptr<EncryptorBase> encryptor,
base::OnceCallback<void(StatusOr<std::pair<uint32_t, std::string>>)>
cb,
StatusOr<std::string> asymmetric_key_result) {
if (!asymmetric_key_result.ok()) {
std::move(cb).Run(asymmetric_key_result.status());
return;
}
const auto& asymmetric_key = asymmetric_key_result.ValueOrDie();
std::move(cb).Run(std::make_pair(
base::PersistentHash(asymmetric_key),
encryptor->EncryptSymmetricKey(symmetric_key, asymmetric_key)));
},
std::string(symmetric_key), base::WrapRefCounted(this), std::move(cb)));
}
} // namespace reporting
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_POLICY_MESSAGING_LAYER_ENCRYPTION_ENCRYPTION_H_
#define CHROME_BROWSER_POLICY_MESSAGING_LAYER_ENCRYPTION_ENCRYPTION_H_
#include <string>
#include <utility>
#include "base/callback.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_refptr.h"
#include "base/optional.h"
#include "base/strings/string_piece.h"
#include "chrome/browser/policy/messaging_layer/util/status.h"
#include "chrome/browser/policy/messaging_layer/util/statusor.h"
#include "components/policy/proto/record.pb.h"
namespace reporting {
// Interface to the encryption.
// Instantiated by an implementation-specific factory:
// StatusOr<scoped_refptr<EncryptorBase>> Create(
// implementation-specific parameters);
// The implementation class should never be used directly by the client code.
class EncryptorBase : public base::RefCountedThreadSafe<EncryptorBase> {
public:
// Encryption record handle, which is created by |OpenRecord| and can accept
// pieces of data to be encrypted as one record by calling |AddToRecord|
// multiple times. Resulting encrypted record is available once |CloseRecord|
// is called.
class Handle {
public:
// Adds piece of data to the record.
virtual void AddToRecord(base::StringPiece data,
base::OnceCallback<void(Status)> cb) = 0;
// Closes and encrypts the record, hands over the data (encrypted with
// symmetric key) and the key (encrypted with asymmetric key) to be recorded
// by the client (or Status if unsuccessful). Self-destructs after the
// callback.
virtual void CloseRecord(
base::OnceCallback<void(StatusOr<EncryptedRecord>)> cb) = 0;
protected:
explicit Handle(scoped_refptr<EncryptorBase> encryptor);
// Destructor is non-public, because the object can only self-destruct by
// |CloseRecord|.
virtual ~Handle();
EncryptorBase* encryptor() const { return encryptor_.get(); }
private:
scoped_refptr<EncryptorBase> encryptor_;
};
// Delivers public asymmetric key to the implementation.
// To affect specific record, must happen before Handle::CloseRecord
// (it is OK to do it after OpenRecord and Handle::AddToRecord).
// Executes on a sequenced thread, returns with callback.
void UpdateAsymmetricKey(base::StringPiece new_key,
base::OnceCallback<void(Status)> response_cb);
// Factory method creates new record to collect data and encrypt them.
// Hands the Handle raw pointer over to the callback, or error status
// (e.g., “encryption is not enabled yet”).
virtual void OpenRecord(base::OnceCallback<void(StatusOr<Handle*>)> cb) = 0;
// Encrypts symmetric key with asymmetric public key, returns encrypted key
// and the hash of the public key used or error status (e.g., “decryption is
// not enabled yet”)
void EncryptKey(
base::StringPiece key,
base::OnceCallback<void(StatusOr<std::pair<uint32_t, std::string>>)> cb);
protected:
EncryptorBase();
virtual ~EncryptorBase();
private:
friend class base::RefCountedThreadSafe<EncryptorBase>;
// Synchronously encrypts symmetric key with asymmetric.
// Called by |EncryptKey|.
virtual std::string EncryptSymmetricKey(base::StringPiece symmetric_key,
base::StringPiece asymmetric_key) = 0;
// Retrieves the current public key.
// Executes on a sequenced thread, returns with callback.
void RetrieveAsymmetricKey(
base::OnceCallback<void(StatusOr<std::string>)> cb);
// Public key used for asymmetric encryption of symmetric key.
base::Optional<std::string> asymmetric_key_;
// Sequential task runner for all asymmetric_key_ activities: update, read.
scoped_refptr<base::SequencedTaskRunner>
asymmetric_key_sequenced_task_runner_;
SEQUENCE_CHECKER(asymmetric_key_sequence_checker_);
};
} // namespace reporting
#endif // CHROME_BROWSER_POLICY_MESSAGING_LAYER_ENCRYPTION_ENCRYPTION_H_
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/policy/messaging_layer/encryption/fake_decryption.h"
#include "base/memory/ptr_util.h"
#include "chrome/browser/policy/messaging_layer/util/status.h"
#include "chrome/browser/policy/messaging_layer/util/statusor.h"
namespace reporting {
namespace test {
namespace {
// Decryption record handle for FakeDecryptor.
class MockRecordHandle : public DecryptorBase::Handle {
public:
explicit MockRecordHandle(base::StringPiece symmetric_key,
scoped_refptr<DecryptorBase> decryptor)
: Handle(decryptor), symmetric_key_(symmetric_key) {}
MockRecordHandle(const MockRecordHandle& other) = delete;
MockRecordHandle& operator=(const MockRecordHandle& other) = delete;
void AddToRecord(base::StringPiece data,
base::OnceCallback<void(Status)> cb) override {
// Decrypt data by XORing every byte with the bytes of symmetric key and add
// to the record.
record_.reserve(record_.size() + data.size());
size_t key_i = 0;
for (const auto& data_byte : data) {
record_.push_back(data_byte ^ symmetric_key_[key_i++]);
if (key_i >= symmetric_key_.size()) {
key_i = 0;
}
}
std::move(cb).Run(Status::StatusOK());
}
void CloseRecord(
base::OnceCallback<void(StatusOr<base::StringPiece>)> cb) override {
std::move(cb).Run(record_);
delete this;
}
private:
// Symmetric key.
const std::string symmetric_key_;
// Accumulated decrypted data.
std::string record_;
};
} // namespace
StatusOr<scoped_refptr<DecryptorBase>> FakeDecryptor::Create() {
return base::WrapRefCounted(new FakeDecryptor());
}
FakeDecryptor::FakeDecryptor() = default;
FakeDecryptor::~FakeDecryptor() = default;
void FakeDecryptor::OpenRecord(base::StringPiece encrypted_key,
base::OnceCallback<void(StatusOr<Handle*>)> cb) {
std::move(cb).Run(new MockRecordHandle(encrypted_key, this));
}
StatusOr<std::string> FakeDecryptor::DecryptKey(
base::StringPiece private_key,
base::StringPiece encrypted_key) {
if (private_key.empty()) {
return Status{error::FAILED_PRECONDITION, "Private key not provided"};
}
// Decrypt symmetric key.
// Private key is assumed to be a reverse string to the public key.
// If symmetric key was encrypted XORing bytes with a public key "012",
// decryption will use private key "210" and XOR will be from the last to the
// first bytes.
std::string unencrypted_key;
unencrypted_key.reserve(encrypted_key.size());
size_t key_i = 0;
for (const auto& key_byte : encrypted_key) {
unencrypted_key.push_back(key_byte ^
private_key[private_key.size() - ++key_i]);
if (key_i >= private_key.size()) {
key_i = 0;
}
}
return unencrypted_key;
}
} // namespace test
} // namespace reporting
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_POLICY_MESSAGING_LAYER_ENCRYPTION_FAKE_DECRYPTION_H_
#define CHROME_BROWSER_POLICY_MESSAGING_LAYER_ENCRYPTION_FAKE_DECRYPTION_H_
#include <string>
#include "base/callback.h"
#include "base/memory/scoped_refptr.h"
#include "base/optional.h"
#include "base/strings/string_piece.h"
#include "chrome/browser/policy/messaging_layer/encryption/decryption.h"
#include "chrome/browser/policy/messaging_layer/util/status.h"
#include "chrome/browser/policy/messaging_layer/util/statusor.h"
namespace reporting {
namespace test {
// Fake implementation of DecryptorBase, intended for use in tests of
// reporting client.
// Key decryption with asymmetric private key is done by per-byte XOR in reverse
// order: public and private key are reverse, so if the encryption used XOR with
// public key "012", decryption will use private key "210" and XOR will be from
// the last to the first bytes.
// Record decryption with symmetric key is done by per-byte XOR.
class FakeDecryptor : public DecryptorBase {
public:
// Factory method
static StatusOr<scoped_refptr<DecryptorBase>> Create();
void OpenRecord(base::StringPiece encrypted_key,
base::OnceCallback<void(StatusOr<Handle*>)> cb) override;
StatusOr<std::string> DecryptKey(base::StringPiece private_key,
base::StringPiece encrypted_key) override;
private:
FakeDecryptor();
~FakeDecryptor() override;
};
} // namespace test
} // namespace reporting
#endif // CHROME_BROWSER_POLICY_MESSAGING_LAYER_ENCRYPTION_FAKE_DECRYPTION_H_
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/policy/messaging_layer/encryption/fake_encryption.h"
#include "base/bind.h"
#include "base/callback.h"
#include "base/hash/hash.h"
#include "base/memory/ptr_util.h"
#include "chrome/browser/policy/messaging_layer/util/status.h"
#include "chrome/browser/policy/messaging_layer/util/statusor.h"
#include "crypto/random.h"
namespace reporting {
namespace test {
namespace {
// Encryption record handle for FakeEncryptor.
class MockRecordHandle : public EncryptorBase::Handle {
public:
explicit MockRecordHandle(base::StringPiece symmetric_key,
scoped_refptr<EncryptorBase> encryptor)
: Handle(encryptor), symmetric_key_(symmetric_key) {}
MockRecordHandle(const MockRecordHandle& other) = delete;
MockRecordHandle& operator=(const MockRecordHandle& other) = delete;
void AddToRecord(base::StringPiece data,
base::OnceCallback<void(Status)> cb) override {
// Encrypt new data by XORing every byte with the symmetric key and add to
// the encrypted record.
record_.reserve(record_.size() + data.size());
size_t key_i = 0;
for (const auto& data_byte : data) {
record_.push_back(data_byte ^ symmetric_key_[key_i++]);
if (key_i >= symmetric_key_.size()) {
key_i = 0;
}
}
std::move(cb).Run(Status::StatusOK());
}
void CloseRecord(
base::OnceCallback<void(StatusOr<EncryptedRecord>)> cb) override {
encryptor()->EncryptKey(
symmetric_key_,
base::BindOnce(
[](MockRecordHandle* handle,
base::OnceCallback<void(StatusOr<EncryptedRecord>)> cb,
StatusOr<std::pair<uint32_t, std::string>>
encrypted_key_result) {
if (!encrypted_key_result.ok()) {
std::move(cb).Run(encrypted_key_result.status());
} else {
EncryptedRecord encrypted_record;
encrypted_record.mutable_encryption_info()->set_public_key_id(
encrypted_key_result.ValueOrDie().first);
encrypted_record.mutable_encryption_info()->set_encryption_key(
encrypted_key_result.ValueOrDie().second);
encrypted_record.set_encrypted_wrapped_record(handle->record_);
std::move(cb).Run(encrypted_record);
}
delete handle;
},
base::Unretained(this), // will self-destruct.
std::move(cb)));
}
private:
// Symmetric key.
const std::string symmetric_key_;
// Accumulated encrypted data.
std::string record_;
};
} // namespace
StatusOr<scoped_refptr<EncryptorBase>> FakeEncryptor::Create() {
return base::WrapRefCounted(new FakeEncryptor());
}
FakeEncryptor::FakeEncryptor() = default;
FakeEncryptor::~FakeEncryptor() = default;
void FakeEncryptor::OpenRecord(base::OnceCallback<void(StatusOr<Handle*>)> cb) {
// For fake implementation just generate random byte string.
constexpr size_t symmetric_key_size = 8;
char symmetric_key[8];
crypto::RandBytes(symmetric_key, symmetric_key_size);
std::move(cb).Run(new MockRecordHandle(
std::string(symmetric_key, symmetric_key_size), this));
}
std::string FakeEncryptor::EncryptSymmetricKey(
base::StringPiece symmetric_key,
base::StringPiece asymmetric_key) {
// Encrypt symmetric key with public asymmetric one: XOR byte by byte.
std::string encrypted_key;
encrypted_key.reserve(symmetric_key.size());
size_t asymmetric_i = 0;
for (const auto& key_byte : symmetric_key) {
encrypted_key.push_back(key_byte ^ asymmetric_key[asymmetric_i++]);
if (asymmetric_i >= asymmetric_key.size()) {
asymmetric_i = 0;
}
}
return encrypted_key;
}
} // namespace test
} // namespace reporting
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_POLICY_MESSAGING_LAYER_ENCRYPTION_FAKE_ENCRYPTION_H_
#define CHROME_BROWSER_POLICY_MESSAGING_LAYER_ENCRYPTION_FAKE_ENCRYPTION_H_
#include <string>
#include "base/callback.h"
#include "base/hash/hash.h"
#include "base/memory/ref_counted.h"
#include "base/strings/string_piece.h"
#include "chrome/browser/policy/messaging_layer/encryption/encryption.h"
#include "chrome/browser/policy/messaging_layer/util/status.h"
#include "chrome/browser/policy/messaging_layer/util/statusor.h"
namespace reporting {
namespace test {
// Fake implementation of EncryptorBase, intended for use in tests of
// reporting client.
// Record encryption with symmetric key is done by per-byte XOR.
// Key encryption with asymmetric public key is also done by per-byte XOR.
class FakeEncryptor : public EncryptorBase {
public:
// Factory method
static StatusOr<scoped_refptr<EncryptorBase>> Create();
void OpenRecord(base::OnceCallback<void(StatusOr<Handle*>)> cb) override;
private:
FakeEncryptor();
~FakeEncryptor() override;
std::string EncryptSymmetricKey(base::StringPiece symmetric_key,
base::StringPiece asymmetric_key) override;
};
} // namespace test
} // namespace reporting
#endif // CHROME_BROWSER_POLICY_MESSAGING_LAYER_ENCRYPTION_FAKE_ENCRYPTION_H_
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/policy/messaging_layer/encryption/fake_encryption.h"
#include "base/bind.h"
#include "base/containers/flat_map.h"
#include "base/hash/hash.h"
#include "base/rand_util.h"
#include "base/strings/strcat.h"
#include "base/synchronization/waitable_event.h"
#include "base/test/task_environment.h"
#include "base/time/time.h"
#include "chrome/browser/policy/messaging_layer/encryption/fake_decryption.h"
#include "chrome/browser/policy/messaging_layer/util/status.h"
#include "chrome/browser/policy/messaging_layer/util/status_macros.h"
#include "chrome/browser/policy/messaging_layer/util/statusor.h"
#include "components/policy/proto/record.pb.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace reporting {
namespace {
// Usage (in tests only):
//
// TestEvent<ResType> e;
// ... Do some async work passing e.cb() as a completion callback of
// base::OnceCallback<void(ResType* res)> type which also may perform
// some other action specified by |done| callback provided by the caller.
// ... = e.result(); // Will wait for e.cb() to be called and return the
// // collected result.
//
// Or, when the callback is not expected to be invoked:
//
// TestEvent<ResType> e(/*expected_to_complete=*/false);
// ... Start work passing e.cb() as a completion callback,
// which will not happen.
//
template <typename ResType>
class TestEvent {
public:
explicit TestEvent(bool expected_to_complete = true)
: expected_to_complete_(expected_to_complete),
completed_(base::WaitableEvent::ResetPolicy::MANUAL,
base::WaitableEvent::InitialState::NOT_SIGNALED) {}
~TestEvent() {
if (expected_to_complete_) {
EXPECT_TRUE(completed_.IsSignaled()) << "Not responded";
} else {
EXPECT_FALSE(completed_.IsSignaled()) << "Responded";
}
}
TestEvent(const TestEvent& other) = delete;
TestEvent& operator=(const TestEvent& other) = delete;
ResType result() {
completed_.Wait();
return std::forward<ResType>(result_);
}
// Completion callback to hand over to the processing method.
base::OnceCallback<void(ResType res)> cb() {
DCHECK(!completed_.IsSignaled());
return base::BindOnce(
[](base::WaitableEvent* completed, ResType* result, ResType res) {
*result = std::forward<ResType>(res);
completed->Signal();
},
base::Unretained(&completed_), base::Unretained(&result_));
}
private:
bool expected_to_complete_;
base::WaitableEvent completed_;
ResType result_;
};
class FakeEncryptionTest : public ::testing::Test {
protected:
FakeEncryptionTest() = default;
void SetUp() override {
auto encryptor_result = test::FakeEncryptor::Create();
ASSERT_OK(encryptor_result.status()) << encryptor_result.status();
encryptor_ = std::move(encryptor_result.ValueOrDie());
auto decryptor_result = test::FakeDecryptor::Create();
ASSERT_OK(decryptor_result.status()) << decryptor_result.status();
decryptor_ = std::move(decryptor_result.ValueOrDie());
}
StatusOr<EncryptedRecord> EncryptSync(base::StringPiece data) {
TestEvent<StatusOr<EncryptorBase::Handle*>> open_encrypt;
encryptor_->OpenRecord(open_encrypt.cb());
auto open_encrypt_result = open_encrypt.result();
RETURN_IF_ERROR(open_encrypt_result.status());
EncryptorBase::Handle* const enc_handle = open_encrypt_result.ValueOrDie();
TestEvent<Status> add_encrypt;
enc_handle->AddToRecord(data, add_encrypt.cb());
RETURN_IF_ERROR(add_encrypt.result());
EncryptedRecord encrypted;
TestEvent<Status> close_encrypt;
enc_handle->CloseRecord(base::BindOnce(
[](EncryptedRecord* encrypted,
base::OnceCallback<void(Status)> close_cb,
StatusOr<EncryptedRecord> result) {
if (!result.ok()) {
std::move(close_cb).Run(result.status());
return;
}
*encrypted = result.ValueOrDie();
std::move(close_cb).Run(Status::StatusOK());
},
base::Unretained(&encrypted), close_encrypt.cb()));
RETURN_IF_ERROR(close_encrypt.result());
return encrypted;
}
StatusOr<std::string> DecryptSync(
std::pair<std::string /*unencrypted_key*/, std::string /*encrypted_data*/>
encrypted) {
TestEvent<StatusOr<DecryptorBase::Handle*>> open_decrypt;
decryptor_->OpenRecord(encrypted.first, open_decrypt.cb());
auto open_decrypt_result = open_decrypt.result();
RETURN_IF_ERROR(open_decrypt_result.status());
DecryptorBase::Handle* const dec_handle = open_decrypt_result.ValueOrDie();
TestEvent<Status> add_decrypt;
dec_handle->AddToRecord(encrypted.second, add_decrypt.cb());
RETURN_IF_ERROR(add_decrypt.result());
std::string decrypted_string;
TestEvent<Status> close_decrypt;
dec_handle->CloseRecord(base::BindOnce(
[](std::string* decrypted_string,
base::OnceCallback<void(Status)> close_cb,
StatusOr<base::StringPiece> result) {
if (!result.ok()) {
std::move(close_cb).Run(result.status());
return;
}
*decrypted_string = std::string(result.ValueOrDie());
std::move(close_cb).Run(Status::StatusOK());
},
base::Unretained(&decrypted_string), close_decrypt.cb()));
RETURN_IF_ERROR(close_decrypt.result());
return decrypted_string;
}
StatusOr<std::string> DecryptMatchingKey(uint32_t public_key_id,
base::StringPiece encrypted_key) {
// Retrieve private key that matches public key hash.
TestEvent<StatusOr<std::string>> retrieve_private_key;
decryptor_->RetrieveMatchingPrivateKey(public_key_id,
retrieve_private_key.cb());
ASSIGN_OR_RETURN(std::string private_key, retrieve_private_key.result());
// Decrypt symmetric key with that private key.
std::string unencrypted_key;
unencrypted_key.reserve(encrypted_key.size());
size_t key_i = 0;
for (const auto& key_byte : encrypted_key) {
unencrypted_key.push_back(key_byte ^
private_key[private_key.size() - ++key_i]);
if (key_i >= private_key.size()) {
key_i = 0;
}
}
return unencrypted_key;
}
scoped_refptr<EncryptorBase> encryptor_;
scoped_refptr<DecryptorBase> decryptor_;
private:
base::test::TaskEnvironment task_environment_{
base::test::TaskEnvironment::TimeSource::MOCK_TIME};
};
TEST_F(FakeEncryptionTest, EncryptAndDecrypt) {
constexpr char kTestString[] = "ABCDEF";
// Public and private key in this test are reversed strings.
constexpr char kPublicKeyString[] = "123";
constexpr char kPrivateKeyString[] = "321";
// Register key pair and provide public key to the encryptor.
TestEvent<Status> record_keys;
decryptor_->RecordKeyPair(kPrivateKeyString, kPublicKeyString,
record_keys.cb());
ASSERT_OK(record_keys.result()) << record_keys.result();
TestEvent<Status> set_public_key;
encryptor_->UpdateAsymmetricKey(kPublicKeyString, set_public_key.cb());
ASSERT_OK(set_public_key.result());
// Encrypt the test string.
const auto encrypted_result = EncryptSync(kTestString);
ASSERT_OK(encrypted_result.status()) << encrypted_result.status();
// Decrypt encrypted_key with private asymmetric key.
auto decrypt_key_result = DecryptMatchingKey(
encrypted_result.ValueOrDie().encryption_info().public_key_id(),
encrypted_result.ValueOrDie().encryption_info().encryption_key());
ASSERT_OK(decrypt_key_result.status()) << decrypt_key_result.status();
// Decrypt back.
const auto decrypted_result = DecryptSync(
std::make_pair(decrypt_key_result.ValueOrDie(),
encrypted_result.ValueOrDie().encrypted_wrapped_record()));
ASSERT_OK(decrypted_result.status()) << decrypted_result.status();
EXPECT_THAT(decrypted_result.ValueOrDie(), ::testing::StrEq(kTestString));
}
TEST_F(FakeEncryptionTest, NoPublicKey) {
constexpr char kTestString[] = "ABCDEF";
// Attempt to encrypt the test string.
const auto encrypted_result = EncryptSync(kTestString);
EXPECT_EQ(encrypted_result.status().error_code(), error::NOT_FOUND);
}
TEST_F(FakeEncryptionTest, EncryptAndDecryptMultiple) {
constexpr const char* kTestStrings[] = {"Rec1", "Rec22", "Rec333",
"Rec4444", "Rec55555", "Rec666666"};
// Public and private key pairs in this test are reversed strings.
constexpr const char* kPublicKeyStrings[] = {"123", "45", "7"};
constexpr const char* kPrivateKeyStrings[] = {"321", "54", "7"};
// Encrypted records.
std::vector<EncryptedRecord> encrypted_records;
// 1. Register first key pair.
TestEvent<Status> record_keys_0;
decryptor_->RecordKeyPair(kPrivateKeyStrings[0], kPublicKeyStrings[0],
record_keys_0.cb());
ASSERT_OK(record_keys_0.result()) << record_keys_0.result();
TestEvent<Status> set_public_key_0;
encryptor_->UpdateAsymmetricKey(kPublicKeyStrings[0], set_public_key_0.cb());
ASSERT_OK(set_public_key_0.result()) << set_public_key_0.result();
// 2. Encrypt 3 test strings.
for (const char* test_string :
{kTestStrings[0], kTestStrings[1], kTestStrings[2]}) {
const auto encrypted_result = EncryptSync(test_string);
ASSERT_OK(encrypted_result.status()) << encrypted_result.status();
encrypted_records.emplace_back(encrypted_result.ValueOrDie());
}
// 3. Register second key pair.
TestEvent<Status> record_keys_1;
decryptor_->RecordKeyPair(kPrivateKeyStrings[1], kPublicKeyStrings[1],
record_keys_1.cb());
ASSERT_OK(record_keys_1.result()) << record_keys_1.result();
TestEvent<Status> set_public_key_1;
encryptor_->UpdateAsymmetricKey(kPublicKeyStrings[1], set_public_key_1.cb());
ASSERT_OK(set_public_key_1.result()) << set_public_key_1.result();
// 4. Encrypt 2 test strings.
for (const char* test_string : {kTestStrings[3], kTestStrings[4]}) {
const auto encrypted_result = EncryptSync(test_string);
ASSERT_OK(encrypted_result.status()) << encrypted_result.status();
encrypted_records.emplace_back(encrypted_result.ValueOrDie());
}
// 3. Register third key pair.
TestEvent<Status> record_keys_2;
decryptor_->RecordKeyPair(kPrivateKeyStrings[2], kPublicKeyStrings[2],
record_keys_2.cb());
ASSERT_OK(record_keys_2.result()) << record_keys_2.result();
TestEvent<Status> set_public_key_2;
encryptor_->UpdateAsymmetricKey(kPublicKeyStrings[2], set_public_key_2.cb());
ASSERT_OK(set_public_key_2.result()) << set_public_key_2.result();
// 4. Encrypt one more test strings.
for (const char* test_string : {kTestStrings[5]}) {
const auto encrypted_result = EncryptSync(test_string);
ASSERT_OK(encrypted_result.status()) << encrypted_result.status();
encrypted_records.emplace_back(encrypted_result.ValueOrDie());
}
// For every encrypted record:
for (size_t i = 0; i < encrypted_records.size(); ++i) {
// Decrypt encrypted_key with private asymmetric key.
auto decrypt_key_result = DecryptMatchingKey(
encrypted_records[i].encryption_info().public_key_id(),
encrypted_records[i].encryption_info().encryption_key());
ASSERT_OK(decrypt_key_result.status()) << decrypt_key_result.status();
// Decrypt back.
const auto decrypted_result = DecryptSync(
std::make_pair(decrypt_key_result.ValueOrDie(),
encrypted_records[i].encrypted_wrapped_record()));
ASSERT_OK(decrypted_result.status()) << decrypted_result.status();
// Verify match.
EXPECT_THAT(decrypted_result.ValueOrDie(),
::testing::StrEq(kTestStrings[i]));
}
}
TEST_F(FakeEncryptionTest, EncryptAndDecryptMultipleParallel) {
// Context of single encryption. Self-destructs upon completion or failure.
class SingleEncryptionContext {
public:
SingleEncryptionContext(
base::StringPiece test_string,
base::StringPiece public_key,
scoped_refptr<EncryptorBase> encryptor,
base::OnceCallback<void(StatusOr<EncryptedRecord>)> response)
: test_string_(test_string),
public_key_(public_key),
encryptor_(encryptor),
response_(std::move(response)) {}
SingleEncryptionContext(const SingleEncryptionContext& other) = delete;
SingleEncryptionContext& operator=(const SingleEncryptionContext& other) =
delete;
~SingleEncryptionContext() {
DCHECK(!response_) << "Self-destruct without prior response";
}
void Start() {
base::ThreadPool::PostTask(
FROM_HERE, base::BindOnce(&SingleEncryptionContext::SetPublicKey,
base::Unretained(this)));
}
private:
void Respond(StatusOr<EncryptedRecord> result) {
std::move(response_).Run(result);
delete this;
}
void SetPublicKey() {
encryptor_->UpdateAsymmetricKey(
public_key_,
base::BindOnce(
[](SingleEncryptionContext* self, Status status) {
if (!status.ok()) {
self->Respond(status);
return;
}
base::ThreadPool::PostTask(
FROM_HERE,
base::BindOnce(&SingleEncryptionContext::OpenRecord,
base::Unretained(self)));
},
base::Unretained(this)));
}
void OpenRecord() {
encryptor_->OpenRecord(base::BindOnce(
[](SingleEncryptionContext* self,
StatusOr<EncryptorBase::Handle*> handle_result) {
if (!handle_result.ok()) {
self->Respond(handle_result.status());
return;
}
base::ThreadPool::PostTask(
FROM_HERE,
base::BindOnce(&SingleEncryptionContext::AddToRecord,
base::Unretained(self),
base::Unretained(handle_result.ValueOrDie())));
},
base::Unretained(this)));
}
void AddToRecord(EncryptorBase::Handle* handle) {
handle->AddToRecord(
test_string_,
base::BindOnce(
[](SingleEncryptionContext* self, EncryptorBase::Handle* handle,
Status status) {
if (!status.ok()) {
self->Respond(status);
return;
}
base::ThreadPool::PostTask(
FROM_HERE,
base::BindOnce(&SingleEncryptionContext::CloseRecord,
base::Unretained(self),
base::Unretained(handle)));
},
base::Unretained(this), base::Unretained(handle)));
}
void CloseRecord(EncryptorBase::Handle* handle) {
handle->CloseRecord(base::BindOnce(
[](SingleEncryptionContext* self,
StatusOr<EncryptedRecord> encryption_result) {
self->Respond(encryption_result);
},
base::Unretained(this)));
}
private:
const std::string test_string_;
const std::string public_key_;
const scoped_refptr<EncryptorBase> encryptor_;
base::OnceCallback<void(StatusOr<EncryptedRecord>)> response_;
};
// Context of single decryption. Self-destructs upon completion or failure.
class SingleDecryptionContext {
public:
SingleDecryptionContext(
const EncryptedRecord& encrypted_record,
scoped_refptr<DecryptorBase> decryptor,
base::OnceCallback<void(StatusOr<base::StringPiece>)> response)
: encrypted_record_(encrypted_record),
decryptor_(decryptor),
response_(std::move(response)) {}
SingleDecryptionContext(const SingleDecryptionContext& other) = delete;
SingleDecryptionContext& operator=(const SingleDecryptionContext& other) =
delete;
~SingleDecryptionContext() {
DCHECK(!response_) << "Self-destruct without prior response";
}
void Start() {
base::ThreadPool::PostTask(
FROM_HERE,
base::BindOnce(&SingleDecryptionContext::RetrieveMatchingPrivateKey,
base::Unretained(this)));
}
private:
void Respond(StatusOr<base::StringPiece> result) {
std::move(response_).Run(result);
delete this;
}
void RetrieveMatchingPrivateKey() {
// Retrieve private key that matches public key hash.
decryptor_->RetrieveMatchingPrivateKey(
encrypted_record_.encryption_info().public_key_id(),
base::BindOnce(
[](SingleDecryptionContext* self,
StatusOr<std::string> private_key_result) {
if (!private_key_result.ok()) {
self->Respond(private_key_result.status());
return;
}
base::ThreadPool::PostTask(
FROM_HERE,
base::BindOnce(
&SingleDecryptionContext::DecryptSymmetricKey,
base::Unretained(self),
private_key_result.ValueOrDie()));
},
base::Unretained(this)));
}
void DecryptSymmetricKey(base::StringPiece private_key) {
// Decrypt symmetric key with that private key.
std::string unencrypted_key;
unencrypted_key.reserve(
encrypted_record_.encryption_info().encryption_key().size());
size_t key_i = 0;
for (const auto& key_byte :
encrypted_record_.encryption_info().encryption_key()) {
unencrypted_key.push_back(key_byte ^
private_key[private_key.size() - ++key_i]);
if (key_i >= private_key.size()) {
key_i = 0;
}
}
base::ThreadPool::PostTask(
FROM_HERE, base::BindOnce(&SingleDecryptionContext::OpenRecord,
base::Unretained(this), unencrypted_key));
}
void OpenRecord(base::StringPiece unencrypted_key) {
decryptor_->OpenRecord(
unencrypted_key,
base::BindOnce(
[](SingleDecryptionContext* self,
StatusOr<DecryptorBase::Handle*> handle_result) {
if (!handle_result.ok()) {
self->Respond(handle_result.status());
return;
}
base::ThreadPool::PostTask(
FROM_HERE,
base::BindOnce(
&SingleDecryptionContext::AddToRecord,
base::Unretained(self),
base::Unretained(handle_result.ValueOrDie())));
},
base::Unretained(this)));
}
void AddToRecord(DecryptorBase::Handle* handle) {
handle->AddToRecord(
encrypted_record_.encrypted_wrapped_record(),
base::BindOnce(
[](SingleDecryptionContext* self, DecryptorBase::Handle* handle,
Status status) {
if (!status.ok()) {
self->Respond(status);
return;
}
base::ThreadPool::PostTask(
FROM_HERE,
base::BindOnce(&SingleDecryptionContext::CloseRecord,
base::Unretained(self),
base::Unretained(handle)));
},
base::Unretained(this), base::Unretained(handle)));
}
void CloseRecord(DecryptorBase::Handle* handle) {
handle->CloseRecord(base::BindOnce(
[](SingleDecryptionContext* self,
StatusOr<base::StringPiece> decryption_result) {
self->Respond(decryption_result);
},
base::Unretained(this)));
}
private:
const EncryptedRecord encrypted_record_;
const scoped_refptr<DecryptorBase> decryptor_;
base::OnceCallback<void(StatusOr<base::StringPiece>)> response_;
};
constexpr std::array<const char*, 6> kTestStrings = {
"Rec1", "Rec22", "Rec333", "Rec4444", "Rec55555", "Rec666666"};
// Public and private key pairs in this test are reversed strings.
constexpr std::array<const char*, 3> kPublicKeyStrings = {"123", "45", "7"};
constexpr std::array<const char*, 3> kPrivateKeyStrings = {"321", "54", "7"};
// Encrypt all records in parallel.
std::vector<TestEvent<StatusOr<EncryptedRecord>>> results(
kTestStrings.size());
for (size_t i = 0; i < kTestStrings.size(); ++i) {
// Choose random key pair.
size_t i_key_pair = base::RandInt(0, kPublicKeyStrings.size() - 1);
(new SingleEncryptionContext(kTestStrings[i], kPublicKeyStrings[i_key_pair],
encryptor_, results[i].cb()))
->Start();
}
// Register all key pairs for decryption.
std::vector<TestEvent<Status>> record_results(kPublicKeyStrings.size());
for (size_t i = 0; i < kPublicKeyStrings.size(); ++i) {
base::ThreadPool::PostTask(
FROM_HERE,
base::BindOnce(
[](const char* public_key_string, const char* private_key_string,
scoped_refptr<DecryptorBase> decryptor,
base::OnceCallback<void(Status)> done_cb) {
decryptor->RecordKeyPair(private_key_string, public_key_string,
std::move(done_cb));
},
kPublicKeyStrings[i], kPrivateKeyStrings[i], decryptor_,
record_results[i].cb()));
}
// Verify registration success.
for (auto& record_result : record_results) {
ASSERT_OK(record_result.result()) << record_result.result();
}
// Decrypt all records in parallel.
std::vector<TestEvent<StatusOr<std::string>>> decryption_results(
kTestStrings.size());
for (size_t i = 0; i < results.size(); ++i) {
// Verify encryption success.
const auto result = results[i].result();
ASSERT_OK(result.status()) << result.status();
// Decrypt and compare encrypted_record.
(new SingleDecryptionContext(
result.ValueOrDie(), decryptor_,
base::BindOnce(
[](base::OnceCallback<void(StatusOr<std::string>)>
decryption_result,
StatusOr<base::StringPiece> result) {
if (!result.ok()) {
std::move(decryption_result).Run(result.status());
return;
}
std::move(decryption_result)
.Run(std::string(result.ValueOrDie()));
},
decryption_results[i].cb())))
->Start();
}
// Verify decryption results.
for (size_t i = 0; i < decryption_results.size(); ++i) {
const auto decryption_result = decryption_results[i].result();
ASSERT_OK(decryption_result.status()) << decryption_result.status();
// Verify data match.
EXPECT_THAT(decryption_result.ValueOrDie(),
::testing::StrEq(kTestStrings[i]));
}
}
} // namespace
} // namespace reporting
......@@ -3399,6 +3399,7 @@ test("unit_tests") {
"../browser/policy/file_selection_dialogs_policy_handler_unittest.cc",
"../browser/policy/homepage_location_policy_handler_unittest.cc",
"../browser/policy/javascript_policy_handler_unittest.cc",
"../browser/policy/messaging_layer/encryption/fake_encryption_unittest.cc",
"../browser/policy/messaging_layer/encryption/test_encryption_module.cc",
"../browser/policy/messaging_layer/encryption/test_encryption_module.h",
"../browser/policy/messaging_layer/public/report_client_unittest.cc",
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment