forked from chromium/chromium
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[u2f] Implement BLE frames and fragments.
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
Showing
7 changed files
with
559 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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_ |
Oops, something went wrong.