blob: bfcb89ca895ec44cce12e7e7711a457ec1d9337a [file] [log] [blame]
// Copyright 2021 The Chromium OS 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 "missive/encryption/encryption.h"
#include <memory>
#include <string>
#include <utility>
#include <base/bind.h>
#include <base/callback.h>
#include <base/containers/span.h>
#include <base/hash/hash.h>
#include <base/memory/ptr_util.h>
#include <base/strings/strcat.h>
#include <base/strings/string_number_conversions.h>
#include <base/task/post_task.h>
#include <base/task_runner.h>
#include "missive/encryption/primitives.h"
#include "missive/util/status.h"
#include "missive/util/statusor.h"
namespace reporting {
Encryptor::Handle::Handle(scoped_refptr<Encryptor> encryptor)
: encryptor_(encryptor) {}
Encryptor::Handle::~Handle() = default;
void Encryptor::Handle::AddToRecord(base::StringPiece data,
base::OnceCallback<void(Status)> cb) {
// Append new data to the record.
record_.append(data.data(), data.size());
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.ok()) {
std::move(cb).Run(asymmetric_key_result.status());
return;
}
const auto& asymmetric_key = asymmetric_key_result.ValueOrDie();
if (asymmetric_key.first.size() != kKeySize) {
std::move(cb).Run(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|.
uint8_t out_shared_secret[kKeySize];
uint8_t out_generatet_public_value[kKeySize];
if (!ComputeSharedSecret(
reinterpret_cast<const uint8_t*>(asymmetric_key.first.data()),
out_shared_secret, out_generatet_public_value)) {
std::move(cb).Run(
Status(error::DATA_LOSS, "Curve25519 shared secret not derived"));
return;
}
encrypted_record.mutable_encryption_info()->mutable_encryption_key()->assign(
reinterpret_cast<const char*>(out_generatet_public_value), kKeySize);
// Produce symmetric key from shared secret using HKDF.
uint8_t out_symmetric_key[kKeySize];
if (!ProduceSymmetricKey(out_shared_secret, out_symmetric_key)) {
std::move(cb).Run(
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(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(
base::StringPiece 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(
[](base::StringPiece 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_);
StatusOr<std::pair<std::string, PublicKeyId>> response;
// 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(response);
},
std::move(cb),
!encryptor->asymmetric_key_.has_value()
? StatusOr<std::pair<std::string, PublicKeyId>>(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