Skip to content

Commit

Permalink
[u2f] Implement BLE frames and fragments.
Browse files Browse the repository at this point in the history
Bug: 763303
Change-Id: I0d9734c91fd1de5b16d2ae738b4a414acc55646d
Reviewed-on: https://chromium-review.googlesource.com/657425
Reviewed-by: Ken Rockot <rockot@chromium.org>
Reviewed-by: Jan Wilken Dörrie <jdoerrie@chromium.org>
Commit-Queue: Pavel Kalinnikov <pkalinnikov@chromium.org>
Cr-Commit-Position: refs/heads/master@{#505367}
  • Loading branch information
Pavel Kalinnikov authored and Commit Bot committed Sep 29, 2017
1 parent 5591816 commit 21b4a03
Show file tree
Hide file tree
Showing 7 changed files with 559 additions and 2 deletions.
2 changes: 2 additions & 0 deletions device/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ test("device_unittests") {
"sensors/sensor_manager_android_unittest.cc",
"sensors/sensor_manager_chromeos_unittest.cc",
"test/run_all_unittests.cc",
"u2f/u2f_ble_frames_unittest.cc",
]

deps = [
Expand All @@ -90,6 +91,7 @@ test("device_unittests") {
"//device/sensors",
"//device/sensors/public/cpp:full",
"//device/sensors/public/interfaces",
"//device/u2f",
"//mojo/common",
"//mojo/edk/system",
"//mojo/public/cpp/bindings",
Expand Down
14 changes: 14 additions & 0 deletions device/u2f/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ source_set("u2f") {
"u2f_apdu_command.h",
"u2f_apdu_response.cc",
"u2f_apdu_response.h",
"u2f_ble_frames.cc",
"u2f_ble_frames.h",
"u2f_command_type.h",
"u2f_device.cc",
"u2f_device.h",
Expand All @@ -33,6 +35,7 @@ source_set("u2f") {
"//base",
"//crypto",
"//device/base",
"//device/bluetooth",
"//device/hid",
"//net",
]
Expand Down Expand Up @@ -74,3 +77,14 @@ fuzzer_test("u2f_message_fuzzer") {
]
libfuzzer_options = [ "max_len=2048" ]
}

fuzzer_test("u2f_ble_frames_fuzzer") {
sources = [
"u2f_ble_frames_fuzzer.cc",
]
deps = [
":u2f",
"//net",
]
libfuzzer_options = [ "max_len=65535" ]
}
149 changes: 149 additions & 0 deletions device/u2f/u2f_ble_frames.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
// Copyright 2017 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/u2f/u2f_ble_frames.h"

#include "base/logging.h"
#include "base/numerics/safe_conversions.h"

#include <algorithm>

namespace device {

U2fBleFrame::U2fBleFrame() = default;

U2fBleFrame::U2fBleFrame(U2fCommandType command, std::vector<uint8_t> data)
: command_(command), data_(std::move(data)) {}

U2fBleFrame::U2fBleFrame(U2fBleFrame&&) = default;
U2fBleFrame& U2fBleFrame::operator=(U2fBleFrame&&) = default;

U2fBleFrame::~U2fBleFrame() = default;

bool U2fBleFrame::IsValid() const {
switch (command_) {
case U2fCommandType::CMD_PING:
case U2fCommandType::CMD_MSG:
return true;
case U2fCommandType::CMD_KEEPALIVE:
case U2fCommandType::CMD_ERROR:
return data_.size() == 1;
case U2fCommandType::UNDEFINED:
default:
return false;
}
}

U2fBleFrame::KeepaliveCode U2fBleFrame::GetKeepaliveCode() const {
DCHECK_EQ(command_, U2fCommandType::CMD_KEEPALIVE);
DCHECK_EQ(data_.size(), 1u);
return static_cast<KeepaliveCode>(data_[0]);
}

U2fBleFrame::ErrorCode U2fBleFrame::GetErrorCode() const {
DCHECK_EQ(command_, U2fCommandType::CMD_ERROR);
DCHECK_EQ(data_.size(), 1u);
return static_cast<ErrorCode>(data_[0]);
}

std::pair<U2fBleFrameInitializationFragment,
std::vector<U2fBleFrameContinuationFragment>>
U2fBleFrame::ToFragments(size_t max_fragment_size) const {
DCHECK_LE(data_.size(), 0xFFFFu);
DCHECK_GE(max_fragment_size, 3u);

size_t data_fragment_size = std::min(max_fragment_size - 3, data_.size());
U2fBleFrameInitializationFragment initial_fragment(
command_, static_cast<uint16_t>(data_.size()), &data_[0],
data_fragment_size);

size_t num_continuation_fragments = 0;
std::vector<U2fBleFrameContinuationFragment> other_fragments;
for (size_t pos = data_fragment_size; pos < data_.size();
pos += max_fragment_size - 1) {
data_fragment_size = std::min(data_.size() - pos, max_fragment_size - 1);
other_fragments.push_back(
U2fBleFrameContinuationFragment(&data_[pos], data_fragment_size,
(num_continuation_fragments++) & 0x7F));
}

return std::make_pair(initial_fragment, std::move(other_fragments));
}

bool U2fBleFrameInitializationFragment::Parse(
const std::vector<uint8_t>& data,
U2fBleFrameInitializationFragment* fragment) {
if (data.size() < 3)
return false;

const auto command = static_cast<U2fCommandType>(data[0]);
const uint16_t data_length = (static_cast<uint16_t>(data[1]) << 8) + data[2];
if (static_cast<size_t>(data_length) + 3 < data.size())
return false;

*fragment = U2fBleFrameInitializationFragment(
command, data_length, data.data() + 3, data.size() - 3);
return true;
}

size_t U2fBleFrameInitializationFragment::Serialize(
std::vector<uint8_t>* buffer) const {
buffer->push_back(static_cast<uint8_t>(command_));
buffer->push_back((data_length_ >> 8) & 0xFF);
buffer->push_back(data_length_ & 0xFF);
buffer->insert(buffer->end(), data(), data() + size());
return size() + 3;
}

bool U2fBleFrameContinuationFragment::Parse(
const std::vector<uint8_t>& data,
U2fBleFrameContinuationFragment* fragment) {
if (data.empty())
return false;
const uint8_t sequence = data[0];
*fragment = U2fBleFrameContinuationFragment(data.data() + 1, data.size() - 1,
sequence);
return true;
}

size_t U2fBleFrameContinuationFragment::Serialize(
std::vector<uint8_t>* buffer) const {
buffer->push_back(sequence_);
buffer->insert(buffer->end(), data(), data() + size());
return size() + 1;
}

U2fBleFrameAssembler::U2fBleFrameAssembler(
const U2fBleFrameInitializationFragment& fragment)
: frame_(fragment.command(), std::vector<uint8_t>()) {
std::vector<uint8_t>& data = frame_.data();
data.reserve(fragment.data_length());
data.assign(fragment.data(), fragment.data() + fragment.size());
}

bool U2fBleFrameAssembler::AddFragment(
const U2fBleFrameContinuationFragment& fragment) {
if (fragment.sequence() != sequence_number_)
return false;
if (++sequence_number_ > 0x7F)
sequence_number_ = 0;

std::vector<uint8_t>& data = frame_.data();
if (data.size() + fragment.size() > data.capacity())
return false;
data.insert(data.end(), fragment.data(), fragment.data() + fragment.size());
return true;
}

bool U2fBleFrameAssembler::IsDone() const {
return frame_.data().size() == frame_.data().capacity();
}

U2fBleFrame* U2fBleFrameAssembler::GetFrame() {
return IsDone() ? &frame_ : nullptr;
}

U2fBleFrameAssembler::~U2fBleFrameAssembler() = default;

} // namespace device
180 changes: 180 additions & 0 deletions device/u2f/u2f_ble_frames.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
// Copyright 2017 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.

#ifndef DEVICE_U2F_U2F_BLE_FRAMES_
#define DEVICE_U2F_U2F_BLE_FRAMES_

#include "base/macros.h"

#include "device/u2f/u2f_command_type.h"

#include <stdint.h>
#include <utility>
#include <vector>

namespace device {

class U2fBleFrameInitializationFragment;
class U2fBleFrameContinuationFragment;

// Encapsulates a frame, i.e., a single request to or response from a U2F
// authenticator, designed to be transported via BLE. The frame is further split
// into fragments (see U2fBleFrameFragment class).
//
// The specification of what constitues a frame can be found here:
// https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-u2f-bt-protocol-v1.2-ps-20170411.html#h2_framing
//
// TODO(crbug/763303): Consider refactoring U2fMessage to support BLE frames.
class U2fBleFrame {
public:
// The values which can be carried in the |data| section of a KEEPALIVE
// message sent from an authenticator.
enum class KeepaliveCode : uint8_t {
// The request is still being processed. The authenticator will be sending
// this message every |kKeepAliveMillis| milliseconds until completion.
PROCESSING = 0x01,
// The authenticator is waiting for the Test of User Presence to complete.
TUP_NEEDED = 0x02,
};

// The types of errors an authenticator can return to the client. Carried in
// the |data| section of an ERROR command.
enum class ErrorCode : uint8_t {
INVALID_CMD = 0x01, // The command in the request is unknown/invalid.
INVALID_PAR = 0x02, // The parameters of the command are invalid/missing.
INVALID_LEN = 0x03, // The length of the request is invalid.
INVALID_SEQ = 0x04, // The sequence number is invalid.
REQ_TIMEOUT = 0x05, // The request timed out.
NA_1 = 0x06, // Value reserved (HID).
NA_2 = 0x0A, // Value reserved (HID).
NA_3 = 0x0B, // Value reserved (HID).
OTHER = 0x7F, // Other, unspecified error.
};

U2fBleFrame();
U2fBleFrame(U2fCommandType command, std::vector<uint8_t> data);

U2fBleFrame(U2fBleFrame&&);
U2fBleFrame& operator=(U2fBleFrame&&);

~U2fBleFrame();

U2fCommandType command() const { return command_; }

bool IsValid() const;
KeepaliveCode GetKeepaliveCode() const;
ErrorCode GetErrorCode() const;

const std::vector<uint8_t>& data() const { return data_; }
std::vector<uint8_t>& data() { return data_; }

// Splits the frame into fragments suitable for sending over BLE. Returns the
// first fragment via |initial_fragment|, and pushes the remaining ones back
// to the |other_fragments| vector.
//
// The |max_fragment_size| parameter ought to be at least 3. The resulting
// fragments' binary sizes will not exceed this value.
std::pair<U2fBleFrameInitializationFragment,
std::vector<U2fBleFrameContinuationFragment>>
ToFragments(size_t max_fragment_size) const;

private:
U2fCommandType command_ = U2fCommandType::UNDEFINED;
std::vector<uint8_t> data_;

DISALLOW_COPY_AND_ASSIGN(U2fBleFrame);
};

// A single frame sent over BLE may be split over multiple writes and
// notifications because the technology was not designed for large messages.
// This class represents a single fragment. Not to be used directly.
//
// A frame is divided into an initialization fragment and zero, one or more
// continuation fragments. See the below section of the spec for the details:
// https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-u2f-bt-protocol-v1.2-ps-20170411.html#h2_framing-fragmentation
//
// Note: This class and its subclasses don't own the |data|.
class U2fBleFrameFragment {
public:
U2fBleFrameFragment() = default;
~U2fBleFrameFragment() = default;

const uint8_t* data() const { return data_; }
size_t size() const { return size_; }

protected:
U2fBleFrameFragment(const uint8_t* data, size_t size)
: data_(data), size_(size) {}

private:
const uint8_t* data_ = nullptr;
size_t size_ = 0;
};

// An initialization fragment of a frame.
class U2fBleFrameInitializationFragment : public U2fBleFrameFragment {
public:
static bool Parse(const std::vector<uint8_t>& data,
U2fBleFrameInitializationFragment* fragment);

U2fBleFrameInitializationFragment() = default;
U2fBleFrameInitializationFragment(U2fCommandType command,
uint16_t data_length,
const uint8_t* fragment_data,
size_t fragment_size)
: U2fBleFrameFragment(fragment_data, fragment_size),
command_(command),
data_length_(data_length) {}

U2fCommandType command() const { return command_; }
uint16_t data_length() const { return data_length_; }

size_t Serialize(std::vector<uint8_t>* buffer) const;

private:
U2fCommandType command_ = U2fCommandType::UNDEFINED;
uint16_t data_length_ = 0;
};

// A continuation fragment of a frame.
class U2fBleFrameContinuationFragment : public U2fBleFrameFragment {
public:
static bool Parse(const std::vector<uint8_t>& data,
U2fBleFrameContinuationFragment* fragment);

U2fBleFrameContinuationFragment() = default;
U2fBleFrameContinuationFragment(const uint8_t* data,
size_t size,
uint8_t sequence)
: U2fBleFrameFragment(data, size), sequence_(sequence) {}

uint8_t sequence() const { return sequence_; }

size_t Serialize(std::vector<uint8_t>* buffer) const;

private:
uint8_t sequence_ = 0;
};

// The helper used to construct a U2fBleFrame from a sequence of its fragments.
class U2fBleFrameAssembler {
public:
U2fBleFrameAssembler(const U2fBleFrameInitializationFragment& fragment);
~U2fBleFrameAssembler();

bool IsDone() const;

bool AddFragment(const U2fBleFrameContinuationFragment& fragment);
U2fBleFrame* GetFrame();

private:
uint8_t sequence_number_ = 0;
U2fBleFrame frame_;

DISALLOW_COPY_AND_ASSIGN(U2fBleFrameAssembler);
};

} // namespace device

#endif // DEVICE_U2F_U2F_BLE_FRAMES_
Loading

0 comments on commit 21b4a03

Please sign in to comment.