Skip to content

Commit

Permalink
Add builders to APDU command class
Browse files Browse the repository at this point in the history
Sign, Register and GetVersion APDU messages
are defined as part of the FIDO U2f specification.
Adding static builder methods for the creation
of these messages.

R=reillyg@chromium.org
BUG=686306

Review-Url: https://codereview.chromium.org/2665493002
Cr-Commit-Position: refs/heads/master@{#447122}
  • Loading branch information
piperc authored and Commit bot committed Jan 31, 2017
1 parent 5815212 commit 4889a81
Show file tree
Hide file tree
Showing 3 changed files with 176 additions and 6 deletions.
80 changes: 75 additions & 5 deletions device/u2f/u2f_apdu_command.cc
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ namespace device {
scoped_refptr<U2fApduCommand> U2fApduCommand::CreateFromMessage(
const std::vector<uint8_t>& message) {
uint16_t data_length = 0;
size_t index = 0, response_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;
Expand Down Expand Up @@ -59,14 +61,18 @@ scoped_refptr<U2fApduCommand> U2fApduCommand::CreateFromMessage(
// 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 make_scoped_refptr(
new U2fApduCommand(cla, ins, p1, p2, response_length, std::move(data)));
return make_scoped_refptr(new U2fApduCommand(
cla, ins, p1, p2, response_length, std::move(data), std::move(suffix)));
}

// static
Expand Down Expand Up @@ -98,6 +104,8 @@ std::vector<uint8_t> U2fApduCommand::GetEncodedCommand() const {
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;
}

Expand All @@ -109,14 +117,76 @@ U2fApduCommand::U2fApduCommand(uint8_t cla,
uint8_t p1,
uint8_t p2,
size_t response_length,
std::vector<uint8_t> data)
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)) {}
data_(std::move(data)),
suffix_(std::move(suffix)) {}

U2fApduCommand::~U2fApduCommand() {}

// static
scoped_refptr<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;
}

scoped_refptr<U2fApduCommand> command = Create();
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
scoped_refptr<U2fApduCommand> U2fApduCommand::CreateVersion() {
scoped_refptr<U2fApduCommand> command = Create();
command->set_ins(kInsU2fVersion);
command->set_response_length(kApduMaxResponseLength);
return command;
}

// static
scoped_refptr<U2fApduCommand> U2fApduCommand::CreateLegacyVersion() {
scoped_refptr<U2fApduCommand> command = Create();
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
scoped_refptr<U2fApduCommand> U2fApduCommand::CreateSign(
const std::vector<uint8_t>& appid_digest,
const std::vector<uint8_t>& challenge_digest,
const std::vector<uint8_t>& key_handle) {
if (appid_digest.size() != kAppIdDigestLen ||
challenge_digest.size() != kChallengeDigestLen ||
key_handle.size() > kMaxKeyHandleLength) {
return nullptr;
}

scoped_refptr<U2fApduCommand> command = Create();
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(kP1TupRequiredConsumed);
command->set_data(data);
return command;
}

} // namespace device
31 changes: 30 additions & 1 deletion device/u2f/u2f_apdu_command.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,28 @@ class U2fApduCommand : public base::RefCountedThreadSafe<U2fApduCommand> {
void set_response_length(size_t response_length) {
response_length_ = response_length;
}
void set_suffix(const std::vector<uint8_t>& suffix) { suffix_ = suffix; }
static scoped_refptr<U2fApduCommand> CreateRegister(
const std::vector<uint8_t>& appid_digest,
const std::vector<uint8_t>& challenge_digest);
static scoped_refptr<U2fApduCommand> CreateVersion();
// Early U2F drafts defined a non-ISO 7816-4 conforming layout
static scoped_refptr<U2fApduCommand> CreateLegacyVersion();
static scoped_refptr<U2fApduCommand> CreateSign(
const std::vector<uint8_t>& appid_digest,
const std::vector<uint8_t>& challenge_digest,
const std::vector<uint8_t>& key_handle);

private:
friend class base::RefCountedThreadSafe<U2fApduCommand>;
friend class U2fApduBuilder;
FRIEND_TEST_ALL_PREFIXES(U2fApduTest, TestDeserializeBasic);
FRIEND_TEST_ALL_PREFIXES(U2fApduTest, TestDeserializeComplex);
FRIEND_TEST_ALL_PREFIXES(U2fApduTest, TestSerializeEdgeCases);
FRIEND_TEST_ALL_PREFIXES(U2fApduTest, TestCreateSign);
FRIEND_TEST_ALL_PREFIXES(U2fApduTest, TestCreateRegister);
FRIEND_TEST_ALL_PREFIXES(U2fApduTest, TestCreateVersion);
FRIEND_TEST_ALL_PREFIXES(U2fApduTest, TestCreateLegacyVersion);

static constexpr size_t kApduMinHeader = 4;
static constexpr size_t kApduMaxHeader = 7;
Expand All @@ -55,14 +70,27 @@ class U2fApduCommand : public base::RefCountedThreadSafe<U2fApduCommand> {
static constexpr size_t kApduMaxResponseLength = 65536;
static constexpr size_t kApduMaxLength =
kApduMaxDataLength + kApduMaxHeader + 2;
// APDU instructions
static constexpr uint8_t kInsU2fEnroll = 0x01;
static constexpr uint8_t kInsU2fSign = 0x02;
static constexpr uint8_t kInsU2fVersion = 0x03;
// P1 instructions
static constexpr uint8_t kP1TupRequired = 0x01;
static constexpr uint8_t kP1TupConsumed = 0x02;
static constexpr uint8_t kP1TupRequiredConsumed =
kP1TupRequired | kP1TupConsumed;
static constexpr size_t kMaxKeyHandleLength = 255;
static constexpr size_t kChallengeDigestLen = 32;
static constexpr size_t kAppIdDigestLen = 32;

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> data,
std::vector<uint8_t> suffix);
~U2fApduCommand();

uint8_t cla_;
Expand All @@ -71,6 +99,7 @@ class U2fApduCommand : public base::RefCountedThreadSafe<U2fApduCommand> {
uint8_t p2_;
size_t response_length_;
std::vector<uint8_t> data_;
std::vector<uint8_t> suffix_;
};
} // namespace device

Expand Down
71 changes: 71 additions & 0 deletions device/u2f/u2f_apdu_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -181,4 +181,75 @@ TEST_F(U2fApduTest, TestSerializeEdgeCases) {
U2fApduCommand::CreateFromMessage(cmd->GetEncodedCommand())
->GetEncodedCommand()));
}

TEST_F(U2fApduTest, TestCreateSign) {
std::vector<uint8_t> appid(U2fApduCommand::kAppIdDigestLen, 0x01);
std::vector<uint8_t> challenge(U2fApduCommand::kChallengeDigestLen, 0xff);
std::vector<uint8_t> key_handle(U2fApduCommand::kMaxKeyHandleLength);

scoped_refptr<U2fApduCommand> cmd =
U2fApduCommand::CreateSign(appid, challenge, key_handle);
ASSERT_NE(nullptr, cmd);
EXPECT_THAT(U2fApduCommand::CreateFromMessage(cmd->GetEncodedCommand())
->GetEncodedCommand(),
testing::ContainerEq(cmd->GetEncodedCommand()));
// Expect null result with incorrectly sized key handle
key_handle.push_back(0x0f);
cmd = U2fApduCommand::CreateSign(appid, challenge, key_handle);
EXPECT_EQ(nullptr, cmd);
key_handle.pop_back();
// Expect null result with incorrectly sized appid
appid.pop_back();
cmd = U2fApduCommand::CreateSign(appid, challenge, key_handle);
EXPECT_EQ(nullptr, cmd);
appid.push_back(0xff);
// Expect null result with incorrectly sized challenge
challenge.push_back(0x0);
cmd = U2fApduCommand::CreateSign(appid, challenge, key_handle);
EXPECT_EQ(nullptr, cmd);
}

TEST_F(U2fApduTest, TestCreateRegister) {
std::vector<uint8_t> appid(U2fApduCommand::kAppIdDigestLen, 0x01);
std::vector<uint8_t> challenge(U2fApduCommand::kChallengeDigestLen, 0xff);
scoped_refptr<U2fApduCommand> cmd =
U2fApduCommand::CreateRegister(appid, challenge);
ASSERT_NE(nullptr, cmd);
EXPECT_THAT(U2fApduCommand::CreateFromMessage(cmd->GetEncodedCommand())
->GetEncodedCommand(),
testing::ContainerEq(cmd->GetEncodedCommand()));
// Expect null result with incorrectly sized appid
appid.push_back(0xff);
cmd = U2fApduCommand::CreateRegister(appid, challenge);
EXPECT_EQ(nullptr, cmd);
appid.pop_back();
// Expect null result with incorrectly sized challenge
challenge.push_back(0xff);
cmd = U2fApduCommand::CreateRegister(appid, challenge);
EXPECT_EQ(nullptr, cmd);
}

TEST_F(U2fApduTest, TestCreateVersion) {
scoped_refptr<U2fApduCommand> cmd = U2fApduCommand::CreateVersion();
std::vector<uint8_t> expected = {
0x0, U2fApduCommand::kInsU2fVersion, 0x0, 0x0, 0x0, 0x0, 0x0};

EXPECT_THAT(expected, testing::ContainerEq(cmd->GetEncodedCommand()));
EXPECT_THAT(U2fApduCommand::CreateFromMessage(cmd->GetEncodedCommand())
->GetEncodedCommand(),
testing::ContainerEq(cmd->GetEncodedCommand()));
}

TEST_F(U2fApduTest, TestCreateLegacyVersion) {
scoped_refptr<U2fApduCommand> cmd = U2fApduCommand::CreateLegacyVersion();
// Legacy version command contains 2 extra null bytes compared to ISO 7816-4
// format
std::vector<uint8_t> expected = {
0x0, U2fApduCommand::kInsU2fVersion, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0};

EXPECT_THAT(expected, testing::ContainerEq(cmd->GetEncodedCommand()));
EXPECT_THAT(U2fApduCommand::CreateFromMessage(cmd->GetEncodedCommand())
->GetEncodedCommand(),
testing::ContainerEq(cmd->GetEncodedCommand()));
}
} // namespace device

0 comments on commit 4889a81

Please sign in to comment.