blob: 7c20f876d6ab92f7d55279de9d0df08a4e74f26e [file] [log] [blame]
// 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 "base/memory/ptr_util.h"
#include "u2f_apdu_command.h"
namespace device {
std::unique_ptr<U2fApduCommand> U2fApduCommand::CreateFromMessage(
const std::vector<uint8_t>& message) {
uint16_t data_length = 0;
size_t index = 0;
size_t response_length = 0;
std::vector<uint8_t> data;
std::vector<uint8_t> suffix;
if (message.size() < kApduMinHeader || message.size() > kApduMaxLength)
return nullptr;
uint8_t cla = message[index++];
uint8_t ins = message[index++];
uint8_t p1 = message[index++];
uint8_t p2 = message[index++];
switch (message.size()) {
// No data present; no expected response
case kApduMinHeader:
break;
// Invalid encoding sizes
case kApduMinHeader + 1:
case kApduMinHeader + 2:
return nullptr;
// No data present; response expected
case kApduMinHeader + 3:
// Fifth byte must be 0
if (message[index++] != 0)
return nullptr;
response_length = message[index++] << 8;
response_length |= message[index++];
// Special case where response length of 0x0000 corresponds to 65536
// Defined in ISO7816-4
if (response_length == 0)
response_length = kApduMaxResponseLength;
break;
default:
// Fifth byte must be 0
if (message[index++] != 0)
return nullptr;
data_length = message[index++] << 8;
data_length |= message[index++];
if (message.size() == data_length + index) {
// No response expected
data.insert(data.end(), message.begin() + index, message.end());
} else if (message.size() == data_length + index + 2) {
// Maximum response size is stored in final 2 bytes
data.insert(data.end(), message.begin() + index, message.end() - 2);
index += data_length;
response_length = message[index++] << 8;
response_length |= message[index++];
// Special case where response length of 0x0000 corresponds to 65536
// Defined in ISO7816-4
if (response_length == 0)
response_length = kApduMaxResponseLength;
// Non-ISO7816-4 special legacy case where 2 suffix bytes are passed
// along with a version message
if (data_length == 0 && ins == kInsU2fVersion)
suffix = {0x0, 0x0};
} else {
return nullptr;
}
break;
}
return std::make_unique<U2fApduCommand>(cla, ins, p1, p2, response_length,
std::move(data), std::move(suffix));
}
std::vector<uint8_t> U2fApduCommand::GetEncodedCommand() const {
std::vector<uint8_t> encoded = {cla_, ins_, p1_, p2_};
// If data exists, request size (Lc) is encoded in 3 bytes, with the first
// byte always being null, and the other two bytes being a big-endian
// representation of the request size. If data length is 0, response size (Le)
// will be prepended with a null byte.
if (data_.size() > 0) {
size_t data_length = data_.size();
encoded.push_back(0x0);
if (data_length > kApduMaxDataLength)
data_length = kApduMaxDataLength;
encoded.push_back((data_length >> 8) & 0xff);
encoded.push_back(data_length & 0xff);
encoded.insert(encoded.end(), data_.begin(), data_.begin() + data_length);
} else if (response_length_ > 0) {
encoded.push_back(0x0);
}
if (response_length_ > 0) {
encoded.push_back((response_length_ >> 8) & 0xff);
encoded.push_back(response_length_ & 0xff);
}
// Add suffix, if required, for legacy compatibility
encoded.insert(encoded.end(), suffix_.begin(), suffix_.end());
return encoded;
}
U2fApduCommand::U2fApduCommand()
: cla_(0), ins_(0), p1_(0), p2_(0), response_length_(0) {}
U2fApduCommand::U2fApduCommand(uint8_t cla,
uint8_t ins,
uint8_t p1,
uint8_t p2,
size_t response_length,
std::vector<uint8_t> data,
std::vector<uint8_t> suffix)
: cla_(cla),
ins_(ins),
p1_(p1),
p2_(p2),
response_length_(response_length),
data_(std::move(data)),
suffix_(std::move(suffix)) {}
U2fApduCommand::~U2fApduCommand() = default;
// static
std::unique_ptr<U2fApduCommand> U2fApduCommand::CreateRegister(
const std::vector<uint8_t>& appid_digest,
const std::vector<uint8_t>& challenge_digest) {
if (appid_digest.size() != kAppIdDigestLen ||
challenge_digest.size() != kChallengeDigestLen) {
return nullptr;
}
auto command = std::make_unique<U2fApduCommand>();
std::vector<uint8_t> data(challenge_digest.begin(), challenge_digest.end());
data.insert(data.end(), appid_digest.begin(), appid_digest.end());
command->set_ins(kInsU2fEnroll);
command->set_p1(kP1TupRequiredConsumed);
command->set_data(data);
return command;
}
// static
std::unique_ptr<U2fApduCommand> U2fApduCommand::CreateVersion() {
auto command = std::make_unique<U2fApduCommand>();
command->set_ins(kInsU2fVersion);
command->set_response_length(kApduMaxResponseLength);
return command;
}
// static
std::unique_ptr<U2fApduCommand> U2fApduCommand::CreateLegacyVersion() {
auto command = std::make_unique<U2fApduCommand>();
command->set_ins(kInsU2fVersion);
command->set_response_length(kApduMaxResponseLength);
// Early U2F drafts defined the U2F version command in extended
// length ISO 7816-4 format so 2 additional 0x0 bytes are necessary.
// https://fidoalliance.org/specs/fido-u2f-v1.1-id-20160915/fido-u2f-raw-message-formats-v1.1-id-20160915.html#implementation-considerations
command->set_suffix(std::vector<uint8_t>(2, 0));
return command;
}
// static
std::unique_ptr<U2fApduCommand> U2fApduCommand::CreateSign(
const std::vector<uint8_t>& appid_digest,
const std::vector<uint8_t>& challenge_digest,
const std::vector<uint8_t>& key_handle,
bool check_only) {
if (appid_digest.size() != kAppIdDigestLen ||
challenge_digest.size() != kChallengeDigestLen ||
key_handle.size() > kMaxKeyHandleLength) {
return nullptr;
}
auto command = std::make_unique<U2fApduCommand>();
std::vector<uint8_t> data(challenge_digest.begin(), challenge_digest.end());
data.insert(data.end(), appid_digest.begin(), appid_digest.end());
data.push_back(static_cast<uint8_t>(key_handle.size()));
data.insert(data.end(), key_handle.begin(), key_handle.end());
command->set_ins(kInsU2fSign);
command->set_p1(check_only ? kP1CheckOnly : kP1TupRequiredConsumed);
command->set_data(data);
return command;
}
} // namespace device