| // 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 |