blob: a4dc89053f77b476704ef5e63e5ea1bb6dcc402a [file] [log] [blame]
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/reporting/encryption/encryption.h"
#include <memory>
#include <string>
#include <utility>
#include "base/containers/span.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/hash/hash.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_functions.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_view_util.h"
#include "base/task/task_runner.h"
#include "base/task/thread_pool.h"
#include "base/types/expected.h"
#include "components/reporting/encryption/primitives.h"
#include "components/reporting/util/reporting_errors.h"
#include "components/reporting/util/status.h"
#include "components/reporting/util/statusor.h"
namespace reporting {
Encryptor::Handle::Handle(scoped_refptr<Encryptor> encryptor)
: encryptor_(encryptor) {}
Encryptor::Handle::~Handle() = default;
void Encryptor::Handle::AddToRecord(std::string_view data,
base::OnceCallback<void(Status)> cb) {
// Append new data to the record.
record_.append(data);
std::move(cb).Run(Status::StatusOK());
}
void Encryptor::Handle::CloseRecord(
base::OnceCallback<void(StatusOr<EncryptedRecord>)> cb) {
// Retrieves asymmetric public key to use.
encryptor_->RetrieveAsymmetricKey(base::BindOnce(
&Handle::ProduceEncryptedRecord, base::Unretained(this), std::move(cb)));
}
void Encryptor::Handle::ProduceEncryptedRecord(
base::OnceCallback<void(StatusOr<EncryptedRecord>)> cb,
StatusOr<std::pair<std::string, PublicKeyId>> asymmetric_key_result) {
// Make sure the record self-destructs when returning from this method.
const auto self_destruct = base::WrapUnique(this);
// Validate and accept asymmetric peer key.
if (!asymmetric_key_result.has_value()) {
std::move(cb).Run(
base::unexpected(std::move(asymmetric_key_result).error()));
return;
}
const auto& asymmetric_key = asymmetric_key_result.value();
if (asymmetric_key.first.size() != kKeySize) {
std::move(cb).Run(base::unexpected(Status(
error::INTERNAL,
base::StrCat({"Asymmetric key size mismatch, expected=",
base::NumberToString(kKeySize), " actual=",
base::NumberToString(asymmetric_key.first.size())}))));
return;
}
// Prepare encrypted record.
EncryptedRecord encrypted_record;
encrypted_record.mutable_encryption_info()->set_public_key_id(
asymmetric_key.second);
encrypted_record.mutable_encryption_info()->mutable_encryption_key()->resize(
kKeySize);
// Compute shared secret, store it in |encrypted_record|.
std::array<uint8_t, kKeySize> out_shared_secret;
std::array<uint8_t, kKeySize> out_generated_public_value;
auto peer_pubkey =
*base::as_byte_span(asymmetric_key.first).to_fixed_extent<kKeySize>();
if (!ComputeSharedSecret(peer_pubkey, out_shared_secret,
out_generated_public_value)) {
std::move(cb).Run(base::unexpected(
Status(error::DATA_LOSS, "Curve25519 shared secret not derived")));
base::UmaHistogramEnumeration(
reporting::kUmaDataLossErrorReason,
DataLossErrorReason::FAILED_TO_CREATE_ENCRYPTION_KEY,
DataLossErrorReason::MAX_VALUE);
return;
}
encrypted_record.mutable_encryption_info()->mutable_encryption_key()->assign(
base::as_string_view(out_generated_public_value));
// Produce symmetric key from shared secret using HKDF.
std::array<uint8_t, kKeySize> out_symmetric_key;
if (!ProduceSymmetricKey(out_shared_secret, out_symmetric_key)) {
std::move(cb).Run(base::unexpected(
Status(error::INTERNAL, "Symmetric key production failed")));
return;
}
// Perform symmmetric encryption with the shared secret as a Chacha20Poly1305
// key and place result in |encrypted_record|.
if (!PerformSymmetricEncryption(
out_symmetric_key, record_,
encrypted_record.mutable_encrypted_wrapped_record())) {
std::move(cb).Run(base::unexpected(
Status(error::INTERNAL, "Symmetric encryption failed")));
return;
}
record_.clear(); // Free unused memory.
// Return EncryptedRecord.
std::move(cb).Run(encrypted_record);
}
Encryptor::Encryptor()
: asymmetric_key_sequenced_task_runner_(
base::ThreadPool::CreateSequencedTaskRunner(
{base::TaskPriority::BEST_EFFORT, base::MayBlock()})) {
DETACH_FROM_SEQUENCE(asymmetric_key_sequence_checker_);
}
Encryptor::~Encryptor() = default;
void Encryptor::UpdateAsymmetricKey(
std::string_view new_public_key,
PublicKeyId new_public_key_id,
base::OnceCallback<void(Status)> response_cb) {
if (new_public_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(
[](std::string_view new_public_key, PublicKeyId new_public_key_id,
scoped_refptr<Encryptor> encryptor) {
encryptor->asymmetric_key_ =
std::make_pair(std::string(new_public_key), new_public_key_id);
},
std::string(new_public_key), new_public_key_id,
base::WrapRefCounted(this)));
// Response OK not waiting for the update.
std::move(response_cb).Run(Status::StatusOK());
}
void Encryptor::OpenRecord(base::OnceCallback<void(StatusOr<Handle*>)> cb) {
std::move(cb).Run(new Handle(this));
}
void Encryptor::RetrieveAsymmetricKey(
base::OnceCallback<void(StatusOr<std::pair<std::string, PublicKeyId>>)>
cb) {
// Schedule key retrieval on the sequenced task runner.
asymmetric_key_sequenced_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(
[](base::OnceCallback<void(
StatusOr<std::pair<std::string, PublicKeyId>>)> cb,
scoped_refptr<Encryptor> encryptor) {
DCHECK_CALLED_ON_VALID_SEQUENCE(
encryptor->asymmetric_key_sequence_checker_);
// Schedule response on regular thread pool.
base::ThreadPool::PostTask(
FROM_HERE,
base::BindOnce(
[](base::OnceCallback<void(
StatusOr<std::pair<std::string, PublicKeyId>>)> cb,
StatusOr<std::pair<std::string, PublicKeyId>> response) {
std::move(cb).Run(std::move(response));
},
std::move(cb),
!encryptor->asymmetric_key_.has_value()
? StatusOr<std::pair<std::string, PublicKeyId>>(
base::unexpected(Status(
error::NOT_FOUND, "Asymmetric key not set")))
: encryptor->asymmetric_key_.value()));
},
std::move(cb), base::WrapRefCounted(this)));
}
StatusOr<scoped_refptr<Encryptor>> Encryptor::Create() {
return base::WrapRefCounted(new Encryptor());
}
} // namespace reporting