blob: 2b68193511ba2de1212d8355b86006012d2139c9 [file] [log] [blame]
// 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/encryption.h"
#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 "chrome/browser/policy/messaging_layer/util/status.h"
#include "chrome/browser/policy/messaging_layer/util/statusor.h"
#include "crypto/aead.h"
#include "crypto/openssl_util.h"
#include "third_party/boringssl/src/include/openssl/curve25519.h"
#include "third_party/boringssl/src/include/openssl/digest.h"
#include "third_party/boringssl/src/include/openssl/hkdf.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::string> asymmetric_key_result) {
// Make sure the record self-destructs when returning from this method.
const auto self_destruct = base::WrapUnique(this);
// Validate keys.
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.size() != X25519_PUBLIC_VALUE_LEN) {
std::move(cb).Run(Status(
error::INTERNAL,
base::StrCat({"Asymmetric key size mismatch, expected=",
base::NumberToString(X25519_PUBLIC_VALUE_LEN), " actual=",
base::NumberToString(asymmetric_key.size())})));
return;
}
// Generate new pair of private key and public value.
uint8_t out_public_value[X25519_PUBLIC_VALUE_LEN];
uint8_t out_private_key[X25519_PRIVATE_KEY_LEN];
X25519_keypair(out_public_value, out_private_key);
// Compute shared secret.
uint8_t out_shared_secret[X25519_SHARED_KEY_LEN];
if (!X25519(out_shared_secret, out_private_key,
reinterpret_cast<const uint8_t*>(asymmetric_key.data()))) {
std::move(cb).Run(Status(error::DATA_LOSS, "Curve25519 encryption failed"));
return;
}
// Encrypt the data with symmetric key using AEAD interface.
crypto::Aead aead(crypto::Aead::CHACHA20_POLY1305);
// Produce symmetric key from shared secret using HKDF.
// Since the keys above are only used once, no salt and context is provided.
const auto out_symmetric_key = std::make_unique<uint8_t[]>(aead.KeyLength());
if (!HKDF(out_symmetric_key.get(), aead.KeyLength(), /*digest=*/EVP_sha256(),
out_shared_secret, X25519_SHARED_KEY_LEN,
/*salt=*/nullptr, /*salt_len=*/0,
/*info=*/nullptr, /*info_len=*/0)) {
std::move(cb).Run(
Status(error::INTERNAL, "Symmetric key extraction failed"));
return;
}
// Use the symmetric key for data encryption.
aead.Init(base::make_span(out_symmetric_key.get(), aead.KeyLength()));
// Set nonce to 0s, since a symmetric key is only used once.
// Note: if we ever start reusing the same symmetric key, we will need
// to generate new nonce for every record and transfer it to the peer.
std::string nonce(aead.NonceLength(), 0);
// Prepare encrypted record.
EncryptedRecord encrypted_record;
encrypted_record.mutable_encryption_info()->set_public_key_id(
base::PersistentHash(asymmetric_key));
encrypted_record.mutable_encryption_info()->set_encryption_key(
reinterpret_cast<const char*>(out_public_value), X25519_PUBLIC_VALUE_LEN);
// Encrypt the whole record.
if (!aead.Seal(record_, nonce, std::string(),
encrypted_record.mutable_encrypted_wrapped_record()) ||
encrypted_record.encrypted_wrapped_record().empty()) {
std::move(cb).Run(Status(error::INTERNAL, "Failed to encrypt the record"));
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_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<Encryptor> encryptor) {
encryptor->asymmetric_key_ = std::string(new_key);
},
std::string(new_key), 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::string>)> cb) {
// Schedule key retrieval on the sequenced task runner.
asymmetric_key_sequenced_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(
[](base::OnceCallback<void(StatusOr<std::string>)> cb,
scoped_refptr<Encryptor> 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)));
}
StatusOr<scoped_refptr<Encryptor>> Encryptor::Create() {
// Make sure OpenSSL is initialized, in order to avoid data races later.
crypto::EnsureOpenSSLInit();
return base::WrapRefCounted(new Encryptor());
}
} // namespace reporting