From 4889a81bbe4d67d614fad10e334a5fc64cd409b7 Mon Sep 17 00:00:00 2001 From: piperc Date: Mon, 30 Jan 2017 16:13:04 -0800 Subject: [PATCH] Add builders to APDU command class 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} --- device/u2f/u2f_apdu_command.cc | 80 ++++++++++++++++++++++++++++++--- device/u2f/u2f_apdu_command.h | 31 ++++++++++++- device/u2f/u2f_apdu_unittest.cc | 71 +++++++++++++++++++++++++++++ 3 files changed, 176 insertions(+), 6 deletions(-) diff --git a/device/u2f/u2f_apdu_command.cc b/device/u2f/u2f_apdu_command.cc index cd854825e53d8d..2bb8efe6e1c40a 100644 --- a/device/u2f/u2f_apdu_command.cc +++ b/device/u2f/u2f_apdu_command.cc @@ -9,8 +9,10 @@ namespace device { scoped_refptr U2fApduCommand::CreateFromMessage( const std::vector& message) { uint16_t data_length = 0; - size_t index = 0, response_length = 0; + size_t index = 0; + size_t response_length = 0; std::vector data; + std::vector suffix; if (message.size() < kApduMinHeader || message.size() > kApduMaxLength) return nullptr; @@ -59,14 +61,18 @@ scoped_refptr 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 @@ -98,6 +104,8 @@ std::vector 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; } @@ -109,14 +117,76 @@ U2fApduCommand::U2fApduCommand(uint8_t cla, uint8_t p1, uint8_t p2, size_t response_length, - std::vector data) + std::vector data, + std::vector 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::CreateRegister( + const std::vector& appid_digest, + const std::vector& challenge_digest) { + if (appid_digest.size() != kAppIdDigestLen || + challenge_digest.size() != kChallengeDigestLen) { + return nullptr; + } + + scoped_refptr command = Create(); + std::vector 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::CreateVersion() { + scoped_refptr command = Create(); + command->set_ins(kInsU2fVersion); + command->set_response_length(kApduMaxResponseLength); + return command; +} + +// static +scoped_refptr U2fApduCommand::CreateLegacyVersion() { + scoped_refptr 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(2, 0)); + return command; +} + +// static +scoped_refptr U2fApduCommand::CreateSign( + const std::vector& appid_digest, + const std::vector& challenge_digest, + const std::vector& key_handle) { + if (appid_digest.size() != kAppIdDigestLen || + challenge_digest.size() != kChallengeDigestLen || + key_handle.size() > kMaxKeyHandleLength) { + return nullptr; + } + + scoped_refptr command = Create(); + std::vector data(challenge_digest.begin(), challenge_digest.end()); + data.insert(data.end(), appid_digest.begin(), appid_digest.end()); + data.push_back(static_cast(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 diff --git a/device/u2f/u2f_apdu_command.h b/device/u2f/u2f_apdu_command.h index 06cd95e81cdf1c..98b53ec2677697 100644 --- a/device/u2f/u2f_apdu_command.h +++ b/device/u2f/u2f_apdu_command.h @@ -37,6 +37,17 @@ class U2fApduCommand : public base::RefCountedThreadSafe { void set_response_length(size_t response_length) { response_length_ = response_length; } + void set_suffix(const std::vector& suffix) { suffix_ = suffix; } + static scoped_refptr CreateRegister( + const std::vector& appid_digest, + const std::vector& challenge_digest); + static scoped_refptr CreateVersion(); + // Early U2F drafts defined a non-ISO 7816-4 conforming layout + static scoped_refptr CreateLegacyVersion(); + static scoped_refptr CreateSign( + const std::vector& appid_digest, + const std::vector& challenge_digest, + const std::vector& key_handle); private: friend class base::RefCountedThreadSafe; @@ -44,6 +55,10 @@ class U2fApduCommand : public base::RefCountedThreadSafe { 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; @@ -55,6 +70,18 @@ class U2fApduCommand : public base::RefCountedThreadSafe { 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, @@ -62,7 +89,8 @@ class U2fApduCommand : public base::RefCountedThreadSafe { uint8_t p1, uint8_t p2, size_t response_length, - std::vector data); + std::vector data, + std::vector suffix); ~U2fApduCommand(); uint8_t cla_; @@ -71,6 +99,7 @@ class U2fApduCommand : public base::RefCountedThreadSafe { uint8_t p2_; size_t response_length_; std::vector data_; + std::vector suffix_; }; } // namespace device diff --git a/device/u2f/u2f_apdu_unittest.cc b/device/u2f/u2f_apdu_unittest.cc index a0435eb0d97ef5..366031965af200 100644 --- a/device/u2f/u2f_apdu_unittest.cc +++ b/device/u2f/u2f_apdu_unittest.cc @@ -181,4 +181,75 @@ TEST_F(U2fApduTest, TestSerializeEdgeCases) { U2fApduCommand::CreateFromMessage(cmd->GetEncodedCommand()) ->GetEncodedCommand())); } + +TEST_F(U2fApduTest, TestCreateSign) { + std::vector appid(U2fApduCommand::kAppIdDigestLen, 0x01); + std::vector challenge(U2fApduCommand::kChallengeDigestLen, 0xff); + std::vector key_handle(U2fApduCommand::kMaxKeyHandleLength); + + scoped_refptr 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 appid(U2fApduCommand::kAppIdDigestLen, 0x01); + std::vector challenge(U2fApduCommand::kChallengeDigestLen, 0xff); + scoped_refptr 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 cmd = U2fApduCommand::CreateVersion(); + std::vector 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 cmd = U2fApduCommand::CreateLegacyVersion(); + // Legacy version command contains 2 extra null bytes compared to ISO 7816-4 + // format + std::vector 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