blob: 02deea187df4f6eead9db2e96214271fc781d3d4 [file] [log] [blame]
// Copyright 2018 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 "device/fido/fido_cable_device.h"
#include <utility>
#include "base/command_line.h"
#include "base/strings/string_piece.h"
#include "device/fido/fido_ble_connection.h"
#include "device/fido/fido_ble_frames.h"
#include "device/fido/fido_constants.h"
#include "device/fido/fido_parsing_utils.h"
namespace device {
namespace switches {
constexpr char kEnableCableEncryption[] = "enable-cable-encryption";
} // namespace switches
namespace {
// Maximum size of EncryptionData::read_sequence_num or
// EncryptionData::write_sequence_num allowed. If we encounter
// counter larger than |kMaxCounter| FidoCableDevice should error out.
constexpr size_t kMaxCounter = (1 << 24) - 1;
base::StringPiece ConvertToStringPiece(const std::vector<uint8_t>& data) {
return base::StringPiece(reinterpret_cast<const char*>(data.data()),
data.size());
}
// static
bool IsEncryptionEnabled() {
base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
return command_line->HasSwitch(switches::kEnableCableEncryption);
}
base::Optional<std::vector<uint8_t>> ConstructEncryptionNonce(
base::span<const uint8_t> nonce,
bool is_sender_client,
uint32_t counter) {
if (counter > kMaxCounter)
return base::nullopt;
auto constructed_nonce = fido_parsing_utils::Materialize(nonce);
constructed_nonce.push_back(is_sender_client ? 0x00 : 0x01);
constructed_nonce.push_back(counter >> 16 & 0xFF);
constructed_nonce.push_back(counter >> 8 & 0xFF);
constructed_nonce.push_back(counter & 0xFF);
return constructed_nonce;
}
bool EncryptOutgoingMessage(
const FidoCableDevice::EncryptionData& encryption_data,
std::vector<uint8_t>* message_to_encrypt) {
const auto nonce = ConstructEncryptionNonce(
encryption_data.nonce, true /* is_sender_client */,
encryption_data.write_sequence_num);
if (!nonce)
return false;
DCHECK_EQ(nonce->size(), encryption_data.aes_key.NonceLength());
std::string ciphertext;
bool encryption_success = encryption_data.aes_key.Seal(
ConvertToStringPiece(*message_to_encrypt), ConvertToStringPiece(*nonce),
nullptr /* additional_data */, &ciphertext);
if (!encryption_success)
return false;
message_to_encrypt->assign(ciphertext.begin(), ciphertext.end());
return true;
}
bool DecryptIncomingMessage(
const FidoCableDevice::EncryptionData& encryption_data,
FidoBleFrame* incoming_frame) {
const auto nonce = ConstructEncryptionNonce(
encryption_data.nonce, false /* is_sender_client */,
encryption_data.read_sequence_num);
if (!nonce)
return false;
DCHECK_EQ(nonce->size(), encryption_data.aes_key.NonceLength());
std::string ciphertext;
bool decryption_success = encryption_data.aes_key.Open(
ConvertToStringPiece(incoming_frame->data()),
ConvertToStringPiece(*nonce), nullptr /* additional_data */, &ciphertext);
if (!decryption_success)
return false;
incoming_frame->data().assign(ciphertext.begin(), ciphertext.end());
return true;
}
} // namespace
// FidoCableDevice::EncryptionData ----------------------------------------
FidoCableDevice::EncryptionData::EncryptionData(
std::string session_key,
const std::array<uint8_t, 8>& nonce)
: encryption_key(std::move(session_key)), nonce(nonce) {
DCHECK_EQ(encryption_key.size(), aes_key.KeyLength());
aes_key.Init(&encryption_key);
}
FidoCableDevice::EncryptionData::EncryptionData(EncryptionData&& data) =
default;
FidoCableDevice::EncryptionData& FidoCableDevice::EncryptionData::operator=(
EncryptionData&& other) = default;
FidoCableDevice::EncryptionData::~EncryptionData() = default;
// FidoCableDevice::EncryptionData ----------------------------------------
FidoCableDevice::FidoCableDevice(std::string address,
std::string session_key,
const std::array<uint8_t, 8>& nonce)
: FidoBleDevice(std::move(address)),
encryption_data_(std::move(session_key), nonce),
weak_factory_(this) {}
FidoCableDevice::FidoCableDevice(std::unique_ptr<FidoBleConnection> connection,
std::string session_key,
const std::array<uint8_t, 8>& nonce)
: FidoBleDevice(std::move(connection)),
encryption_data_(std::move(session_key), nonce),
weak_factory_(this) {}
FidoCableDevice::~FidoCableDevice() = default;
void FidoCableDevice::DeviceTransact(std::vector<uint8_t> command,
DeviceCallback callback) {
if (IsEncryptionEnabled()) {
if (!EncryptOutgoingMessage(encryption_data_, &command)) {
state_ = State::kDeviceError;
return;
}
++encryption_data_.write_sequence_num;
}
AddToPendingFrames(FidoBleDeviceCommand::kMsg, std::move(command),
std::move(callback));
}
void FidoCableDevice::OnResponseFrame(FrameCallback callback,
base::Optional<FidoBleFrame> frame) {
// The request is done, time to reset |transaction_|.
ResetTransaction();
state_ = frame ? State::kReady : State::kDeviceError;
if (frame && IsEncryptionEnabled()) {
if (!DecryptIncomingMessage(encryption_data_, &frame.value())) {
state_ = State::kDeviceError;
frame = base::nullopt;
}
++encryption_data_.read_sequence_num;
}
auto self = GetWeakPtr();
std::move(callback).Run(std::move(frame));
// Executing callbacks may free |this|. Check |self| first.
if (self)
Transition();
}
base::WeakPtr<FidoDevice> FidoCableDevice::GetWeakPtr() {
return weak_factory_.GetWeakPtr();
}
} // namespace device