v2_test_util.cc 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800
  1. // Copyright 2020 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "device/fido/cable/v2_test_util.h"
  5. #include <string>
  6. #include <vector>
  7. #include "base/base64url.h"
  8. #include "base/callback.h"
  9. #include "base/check.h"
  10. #include "base/containers/contains.h"
  11. #include "base/memory/raw_ptr.h"
  12. #include "base/memory/weak_ptr.h"
  13. #include "base/strings/string_number_conversions.h"
  14. #include "base/strings/string_piece.h"
  15. #include "components/cbor/reader.h"
  16. #include "components/cbor/values.h"
  17. #include "components/cbor/writer.h"
  18. #include "crypto/random.h"
  19. #include "device/fido/cable/v2_authenticator.h"
  20. #include "device/fido/cable/v2_discovery.h"
  21. #include "device/fido/cable/v2_handshake.h"
  22. #include "device/fido/cable/websocket_adapter.h"
  23. #include "device/fido/fido_constants.h"
  24. #include "device/fido/virtual_ctap2_device.h"
  25. #include "mojo/public/cpp/bindings/pending_remote.h"
  26. #include "mojo/public/cpp/bindings/remote.h"
  27. #include "net/base/net_errors.h"
  28. #include "net/http/http_status_code.h"
  29. #include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
  30. #include "services/network/test/test_network_context.h"
  31. #include "third_party/blink/public/mojom/webauthn/authenticator.mojom.h"
  32. #include "third_party/boringssl/src/include/openssl/ec_key.h"
  33. #include "url/gurl.h"
  34. namespace device::cablev2 {
  35. namespace {
  36. // TestNetworkContext intercepts WebSocket creation calls and simulates a
  37. // caBLEv2 tunnel server.
  38. class TestNetworkContext : public network::TestNetworkContext {
  39. public:
  40. explicit TestNetworkContext(absl::optional<ContactCallback> contact_callback)
  41. : contact_callback_(std::move(contact_callback)) {}
  42. void CreateWebSocket(
  43. const GURL& url,
  44. const std::vector<std::string>& requested_protocols,
  45. const net::SiteForCookies& site_for_cookies,
  46. const net::IsolationInfo& isolation_info,
  47. std::vector<network::mojom::HttpHeaderPtr> additional_headers,
  48. int32_t process_id,
  49. const url::Origin& origin,
  50. uint32_t options,
  51. const net::MutableNetworkTrafficAnnotationTag& traffic_annotation,
  52. mojo::PendingRemote<network::mojom::WebSocketHandshakeClient>
  53. handshake_client,
  54. mojo::PendingRemote<network::mojom::URLLoaderNetworkServiceObserver>
  55. url_loader_network_observer,
  56. mojo::PendingRemote<network::mojom::WebSocketAuthenticationHandler>
  57. auth_handler,
  58. mojo::PendingRemote<network::mojom::TrustedHeaderClient> header_client,
  59. const absl::optional<base::UnguessableToken>& throttling_profile_id)
  60. override {
  61. CHECK(url.has_path());
  62. base::StringPiece path = url.path_piece();
  63. static const char kNewPrefix[] = "/cable/new/";
  64. static const char kConnectPrefix[] = "/cable/connect/";
  65. static const char kContactPrefix[] = "/cable/contact/";
  66. if (path.find(kNewPrefix) == 0) {
  67. path.remove_prefix(sizeof(kNewPrefix) - 1);
  68. CHECK(!base::Contains(connections_, std::string(path)));
  69. connections_.emplace(std::string(path), std::make_unique<Connection>(
  70. Connection::Type::NEW,
  71. std::move(handshake_client)));
  72. } else if (path.find(kConnectPrefix) == 0) {
  73. path.remove_prefix(sizeof(kConnectPrefix) - 1);
  74. // The first part of |path| will be a hex-encoded routing ID followed by a
  75. // '/'. Skip it.
  76. constexpr size_t kRoutingIdComponentSize = 2 * kRoutingIdSize + 1;
  77. CHECK_GE(path.size(), kRoutingIdComponentSize);
  78. path.remove_prefix(kRoutingIdComponentSize);
  79. const auto it = connections_.find(std::string(path));
  80. CHECK(it != connections_.end()) << "Unknown tunnel requested";
  81. it->second->set_peer(std::make_unique<Connection>(
  82. Connection::Type::CONNECT, std::move(handshake_client)));
  83. } else if (path.find(kContactPrefix) == 0) {
  84. path.remove_prefix(sizeof(kContactPrefix) - 1);
  85. CHECK_EQ(additional_headers.size(), 1u);
  86. CHECK_EQ(additional_headers[0]->name, device::kCableClientPayloadHeader);
  87. if (!contact_callback_) {
  88. // Without a contact callback all attempts are rejected with a 410
  89. // status to indicate the the contact ID will never work again.
  90. mojo::Remote<network::mojom::WebSocketHandshakeClient>
  91. bound_handshake_client(std::move(handshake_client));
  92. bound_handshake_client->OnFailure("", net::OK, net::HTTP_GONE);
  93. return;
  94. }
  95. std::vector<uint8_t> client_payload_bytes;
  96. CHECK(base::HexStringToBytes(additional_headers[0]->value,
  97. &client_payload_bytes));
  98. absl::optional<cbor::Value> client_payload =
  99. cbor::Reader::Read(client_payload_bytes);
  100. const cbor::Value::MapValue& map = client_payload->GetMap();
  101. uint8_t tunnel_id[kTunnelIdSize];
  102. crypto::RandBytes(tunnel_id);
  103. connections_.emplace(
  104. base::HexEncode(tunnel_id),
  105. std::make_unique<Connection>(Connection::Type::CONTACT,
  106. std::move(handshake_client)));
  107. const std::vector<uint8_t>& pairing_id_vec =
  108. map.find(cbor::Value(1))->second.GetBytestring();
  109. base::span<const uint8_t, kPairingIDSize> pairing_id(
  110. pairing_id_vec.data(), pairing_id_vec.size());
  111. const std::vector<uint8_t>& client_nonce_vec =
  112. map.find(cbor::Value(2))->second.GetBytestring();
  113. base::span<const uint8_t, kClientNonceSize> client_nonce(
  114. client_nonce_vec.data(), client_nonce_vec.size());
  115. const std::string& request_type_hint =
  116. map.find(cbor::Value(3))->second.GetString();
  117. contact_callback_->Run(tunnel_id, pairing_id, client_nonce,
  118. request_type_hint);
  119. } else {
  120. CHECK(false) << "unexpected path: " << path;
  121. }
  122. }
  123. private:
  124. class Connection : public network::mojom::WebSocket {
  125. public:
  126. enum class Type {
  127. NEW,
  128. CONNECT,
  129. CONTACT,
  130. };
  131. Connection(Type type,
  132. mojo::PendingRemote<network::mojom::WebSocketHandshakeClient>
  133. pending_handshake_client)
  134. : type_(type),
  135. in_watcher_(FROM_HERE, mojo::SimpleWatcher::ArmingPolicy::MANUAL),
  136. out_watcher_(FROM_HERE, mojo::SimpleWatcher::ArmingPolicy::MANUAL),
  137. handshake_client_(std::move(pending_handshake_client)) {
  138. MojoCreateDataPipeOptions options;
  139. memset(&options, 0, sizeof(options));
  140. options.struct_size = sizeof(options);
  141. options.flags = MOJO_CREATE_DATA_PIPE_FLAG_NONE;
  142. options.element_num_bytes = sizeof(uint8_t);
  143. options.capacity_num_bytes = 1 << 16;
  144. CHECK_EQ(mojo::CreateDataPipe(&options, in_producer_, in_),
  145. MOJO_RESULT_OK);
  146. CHECK_EQ(mojo::CreateDataPipe(&options, out_, out_consumer_),
  147. MOJO_RESULT_OK);
  148. in_watcher_.Watch(in_.get(), MOJO_HANDLE_SIGNAL_READABLE,
  149. MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
  150. base::BindRepeating(&Connection::OnInPipeReady,
  151. base::Unretained(this)));
  152. out_watcher_.Watch(out_.get(), MOJO_HANDLE_SIGNAL_WRITABLE,
  153. MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
  154. base::BindRepeating(&Connection::OnOutPipeReady,
  155. base::Unretained(this)));
  156. base::SequencedTaskRunnerHandle::Get()->PostTask(
  157. FROM_HERE, base::BindOnce(&Connection::CompleteConnection,
  158. base::Unretained(this)));
  159. }
  160. void SendMessage(network::mojom::WebSocketMessageType type,
  161. uint64_t length) override {
  162. if (!peer_ || !peer_->connected_) {
  163. pending_messages_.emplace_back(std::make_pair(type, length));
  164. } else {
  165. peer_->client_receiver_->OnDataFrame(/*final=*/true, type, length);
  166. }
  167. if (length > 0) {
  168. buffer_.resize(buffer_.size() + length);
  169. OnInPipeReady(MOJO_RESULT_OK, mojo::HandleSignalsState());
  170. }
  171. }
  172. void StartReceiving() override {}
  173. void StartClosingHandshake(uint16_t code,
  174. const std::string& reason) override {
  175. CHECK(false);
  176. }
  177. void set_peer(std::unique_ptr<Connection> peer) {
  178. CHECK(!peer_);
  179. peer_ownership_ = std::move(peer);
  180. peer_ = peer_ownership_.get();
  181. peer_->set_nonowning_peer(this);
  182. Flush();
  183. }
  184. private:
  185. // name is useful when adding debugging messages. The first party to a
  186. // tunnel is "A" and the second is "B".
  187. const char* name() const {
  188. switch (type_) {
  189. case Type::NEW:
  190. case Type::CONTACT:
  191. return "A";
  192. case Type::CONNECT:
  193. return "B";
  194. }
  195. }
  196. void set_nonowning_peer(Connection* peer) {
  197. CHECK(!peer_);
  198. peer_ = peer;
  199. Flush();
  200. }
  201. void CompleteConnection() {
  202. CHECK(!connected_);
  203. auto response = network::mojom::WebSocketHandshakeResponse::New();
  204. response->selected_protocol = device::kCableWebSocketProtocol;
  205. if (type_ == Type::NEW) {
  206. auto header = network::mojom::HttpHeader::New();
  207. header->name = device::kCableRoutingIdHeader;
  208. std::array<uint8_t, kRoutingIdSize> routing_id = {42};
  209. header->value = base::HexEncode(routing_id);
  210. response->headers.push_back(std::move(header));
  211. }
  212. handshake_client_->OnConnectionEstablished(
  213. socket_.BindNewPipeAndPassRemote(),
  214. client_receiver_.BindNewPipeAndPassReceiver(), std::move(response),
  215. std::move(out_consumer_), std::move(in_producer_));
  216. connected_ = true;
  217. if (peer_) {
  218. peer_->Flush();
  219. }
  220. }
  221. void Flush() {
  222. if (!peer_->connected_) {
  223. return;
  224. }
  225. for (const auto& pending_message : pending_messages_) {
  226. peer_->client_receiver_->OnDataFrame(
  227. /*final=*/true, pending_message.first, pending_message.second);
  228. }
  229. if (!buffer_.empty()) {
  230. peer_->out_watcher_.Arm();
  231. }
  232. }
  233. void OnInPipeReady(MojoResult, const mojo::HandleSignalsState&) {
  234. const size_t todo = buffer_.size() - buffer_i_;
  235. CHECK_GT(todo, 0u);
  236. // We CHECK that the message fits into Mojo's 32-bit lengths because we
  237. // don't expect anything that large in unittests.
  238. uint32_t todo_32 = todo;
  239. CHECK_LE(todo, std::numeric_limits<decltype(todo_32)>::max());
  240. const MojoResult result = in_->ReadData(
  241. &buffer_.data()[buffer_i_], &todo_32, MOJO_READ_DATA_FLAG_NONE);
  242. if (result == MOJO_RESULT_OK) {
  243. buffer_i_ += todo_32;
  244. CHECK_LE(buffer_i_, buffer_.size());
  245. if (peer_ && buffer_i_ > 0) {
  246. peer_->OnOutPipeReady(MOJO_RESULT_OK, mojo::HandleSignalsState());
  247. }
  248. if (buffer_i_ < buffer_.size()) {
  249. in_watcher_.Arm();
  250. } else {
  251. // TODO
  252. }
  253. } else if (result == MOJO_RESULT_SHOULD_WAIT) {
  254. in_watcher_.Arm();
  255. } else {
  256. CHECK(false) << static_cast<int>(result);
  257. }
  258. }
  259. void OnOutPipeReady(MojoResult, const mojo::HandleSignalsState&) {
  260. const size_t todo = peer_->buffer_.size();
  261. if (todo == 0) {
  262. return;
  263. }
  264. uint32_t todo_32 = todo;
  265. const MojoResult result = out_->WriteData(peer_->buffer_.data(), &todo_32,
  266. MOJO_WRITE_DATA_FLAG_NONE);
  267. if (result == MOJO_RESULT_OK) {
  268. if (todo_32 == todo) {
  269. peer_->buffer_.clear();
  270. peer_->buffer_i_ = 0;
  271. } else {
  272. const size_t new_length = todo - todo_32;
  273. memmove(peer_->buffer_.data(), &peer_->buffer_.data()[todo_32],
  274. new_length);
  275. peer_->buffer_.resize(new_length);
  276. peer_->buffer_i_ -= todo_32;
  277. }
  278. if (!peer_->buffer_.empty()) {
  279. out_watcher_.Arm();
  280. }
  281. } else if (result == MOJO_RESULT_SHOULD_WAIT) {
  282. out_watcher_.Arm();
  283. } else if (result == MOJO_RESULT_FAILED_PRECONDITION) {
  284. // The reader has closed. Drop the message.
  285. } else {
  286. CHECK(false) << static_cast<int>(result);
  287. }
  288. }
  289. const Type type_;
  290. bool connected_ = false;
  291. std::unique_ptr<Connection> peer_ownership_;
  292. std::vector<uint8_t> buffer_;
  293. std::vector<std::pair<network::mojom::WebSocketMessageType, uint64_t>>
  294. pending_messages_;
  295. size_t buffer_i_ = 0;
  296. mojo::SimpleWatcher in_watcher_;
  297. mojo::SimpleWatcher out_watcher_;
  298. raw_ptr<Connection> peer_ = nullptr;
  299. mojo::Remote<network::mojom::WebSocketHandshakeClient> handshake_client_;
  300. mojo::Remote<network::mojom::WebSocketClient> client_receiver_;
  301. mojo::Receiver<network::mojom::WebSocket> socket_{this};
  302. mojo::ScopedDataPipeConsumerHandle in_;
  303. mojo::ScopedDataPipeProducerHandle in_producer_;
  304. mojo::ScopedDataPipeProducerHandle out_;
  305. mojo::ScopedDataPipeConsumerHandle out_consumer_;
  306. };
  307. std::map<std::string, std::unique_ptr<Connection>> connections_;
  308. const absl::optional<ContactCallback> contact_callback_;
  309. };
  310. class DummyBLEAdvert
  311. : public device::cablev2::authenticator::Platform::BLEAdvert {};
  312. // TestPlatform implements the platform support for caBLEv2 by forwarding
  313. // messages to the given |VirtualCtap2Device|.
  314. class TestPlatform : public authenticator::Platform {
  315. public:
  316. TestPlatform(Discovery::AdvertEventStream::Callback ble_advert_callback,
  317. device::VirtualCtap2Device* ctap2_device,
  318. authenticator::Observer* observer)
  319. : ble_advert_callback_(ble_advert_callback),
  320. ctap2_device_(ctap2_device),
  321. observer_(observer) {}
  322. void MakeCredential(
  323. blink::mojom::PublicKeyCredentialCreationOptionsPtr params,
  324. MakeCredentialCallback callback) override {
  325. device::CtapMakeCredentialRequest request(
  326. /*client_data_json=*/"", std::move(params->relying_party),
  327. std::move(params->user),
  328. PublicKeyCredentialParams(std::move(params->public_key_parameters)));
  329. CHECK_EQ(request.client_data_hash.size(), params->challenge.size());
  330. memcpy(request.client_data_hash.data(), params->challenge.data(),
  331. params->challenge.size());
  332. request.resident_key_required =
  333. !params->authenticator_selection
  334. ? false
  335. : params->authenticator_selection->resident_key ==
  336. ResidentKeyRequirement::kRequired;
  337. std::pair<device::CtapRequestCommand, absl::optional<cbor::Value>>
  338. request_cbor = AsCTAPRequestValuePair(request);
  339. ctap2_device_->DeviceTransact(
  340. ToCTAP2Command(std::move(request_cbor)),
  341. base::BindOnce(&TestPlatform::OnMakeCredentialResult,
  342. weak_factory_.GetWeakPtr(), std::move(callback)));
  343. }
  344. void GetAssertion(blink::mojom::PublicKeyCredentialRequestOptionsPtr params,
  345. GetAssertionCallback callback) override {
  346. device::CtapGetAssertionRequest request(std::move(params->relying_party_id),
  347. /* client_data_json= */ "");
  348. request.allow_list = std::move(params->allow_credentials);
  349. CHECK_EQ(request.client_data_hash.size(), params->challenge.size());
  350. memcpy(request.client_data_hash.data(), params->challenge.data(),
  351. params->challenge.size());
  352. std::pair<device::CtapRequestCommand, absl::optional<cbor::Value>>
  353. request_cbor = AsCTAPRequestValuePair(request);
  354. ctap2_device_->DeviceTransact(
  355. ToCTAP2Command(std::move(request_cbor)),
  356. base::BindOnce(&TestPlatform::OnGetAssertionResult,
  357. weak_factory_.GetWeakPtr(), std::move(callback)));
  358. }
  359. void OnStatus(Status status) override {
  360. if (observer_) {
  361. observer_->OnStatus(status);
  362. }
  363. }
  364. void OnCompleted(absl::optional<Error> maybe_error) override {
  365. if (observer_) {
  366. observer_->OnCompleted(maybe_error);
  367. }
  368. }
  369. std::unique_ptr<authenticator::Platform::BLEAdvert> SendBLEAdvert(
  370. base::span<const uint8_t, kAdvertSize> payload) override {
  371. base::SequencedTaskRunnerHandle::Get()->PostTask(
  372. FROM_HERE,
  373. base::BindOnce(
  374. &TestPlatform::DoSendBLEAdvert, weak_factory_.GetWeakPtr(),
  375. device::fido_parsing_utils::Materialize<EXTENT(payload)>(payload)));
  376. return std::make_unique<DummyBLEAdvert>();
  377. }
  378. private:
  379. void DoSendBLEAdvert(base::span<const uint8_t, kAdvertSize> advert) {
  380. ble_advert_callback_.Run(advert);
  381. }
  382. std::vector<uint8_t> ToCTAP2Command(
  383. const std::pair<device::CtapRequestCommand, absl::optional<cbor::Value>>&
  384. parts) {
  385. std::vector<uint8_t> ret;
  386. if (parts.second.has_value()) {
  387. absl::optional<std::vector<uint8_t>> cbor_bytes =
  388. cbor::Writer::Write(std::move(*parts.second));
  389. ret.swap(*cbor_bytes);
  390. }
  391. ret.insert(ret.begin(), static_cast<uint8_t>(parts.first));
  392. return ret;
  393. }
  394. void OnMakeCredentialResult(MakeCredentialCallback callback,
  395. absl::optional<std::vector<uint8_t>> result) {
  396. if (!result || result->empty()) {
  397. std::move(callback).Run(
  398. static_cast<uint32_t>(device::CtapDeviceResponseCode::kCtap2ErrOther),
  399. base::span<const uint8_t>());
  400. return;
  401. }
  402. const base::span<const uint8_t> payload = *result;
  403. if (payload.size() == 1 ||
  404. payload[0] !=
  405. static_cast<uint8_t>(device::CtapDeviceResponseCode::kSuccess)) {
  406. std::move(callback).Run(payload[0], base::span<const uint8_t>());
  407. return;
  408. }
  409. absl::optional<cbor::Value> v = cbor::Reader::Read(payload.subspan(1));
  410. const cbor::Value::MapValue& in_map = v->GetMap();
  411. cbor::Value::MapValue out_map;
  412. out_map.emplace("fmt", in_map.find(cbor::Value(1))->second.GetString());
  413. out_map.emplace("authData",
  414. in_map.find(cbor::Value(2))->second.GetBytestring());
  415. out_map.emplace("attStmt", in_map.find(cbor::Value(3))->second.GetMap());
  416. absl::optional<std::vector<uint8_t>> attestation_obj =
  417. cbor::Writer::Write(cbor::Value(std::move(out_map)));
  418. std::move(callback).Run(
  419. static_cast<uint32_t>(device::CtapDeviceResponseCode::kSuccess),
  420. *attestation_obj);
  421. }
  422. void OnGetAssertionResult(GetAssertionCallback callback,
  423. absl::optional<std::vector<uint8_t>> result) {
  424. if (!result || result->empty()) {
  425. std::move(callback).Run(
  426. static_cast<uint32_t>(device::CtapDeviceResponseCode::kCtap2ErrOther),
  427. nullptr);
  428. return;
  429. }
  430. const base::span<const uint8_t> payload = *result;
  431. if (payload.size() == 1 ||
  432. payload[0] !=
  433. static_cast<uint8_t>(device::CtapDeviceResponseCode::kSuccess)) {
  434. std::move(callback).Run(payload[0], nullptr);
  435. return;
  436. }
  437. auto response = blink::mojom::GetAssertionAuthenticatorResponse::New();
  438. response->info = blink::mojom::CommonCredentialInfo::New();
  439. absl::optional<cbor::Value> v = cbor::Reader::Read(payload.subspan(1));
  440. const cbor::Value::MapValue& in_map = v->GetMap();
  441. auto cred_id_it = in_map.find(cbor::Value(1));
  442. response->info->raw_id = cred_id_it->second.GetMap()
  443. .find(cbor::Value("id"))
  444. ->second.GetBytestring();
  445. response->info->authenticator_data =
  446. in_map.find(cbor::Value(2))->second.GetBytestring();
  447. response->signature = in_map.find(cbor::Value(3))->second.GetBytestring();
  448. auto user_it = in_map.find(cbor::Value(4));
  449. if (user_it != in_map.end()) {
  450. response->user_handle = user_it->second.GetMap()
  451. .find(cbor::Value("id"))
  452. ->second.GetBytestring();
  453. }
  454. std::move(callback).Run(
  455. static_cast<uint32_t>(device::CtapDeviceResponseCode::kSuccess),
  456. std::move(response));
  457. }
  458. Discovery::AdvertEventStream::Callback ble_advert_callback_;
  459. const raw_ptr<device::VirtualCtap2Device> ctap2_device_;
  460. const raw_ptr<authenticator::Observer> observer_;
  461. base::WeakPtrFactory<TestPlatform> weak_factory_{this};
  462. };
  463. } // namespace
  464. namespace authenticator {
  465. namespace {
  466. class LateLinkingDevice : public authenticator::Transaction {
  467. public:
  468. LateLinkingDevice(CtapDeviceResponseCode ctap_error,
  469. std::unique_ptr<Platform> platform,
  470. network::mojom::NetworkContext* network_context,
  471. base::span<const uint8_t> qr_secret,
  472. base::span<const uint8_t, kP256X962Length> peer_identity)
  473. : ctap_error_(ctap_error),
  474. platform_(std::move(platform)),
  475. network_context_(network_context),
  476. tunnel_id_(device::cablev2::Derive<EXTENT(tunnel_id_)>(
  477. qr_secret,
  478. base::span<uint8_t>(),
  479. DerivedValueType::kTunnelID)),
  480. eid_key_(device::cablev2::Derive<EXTENT(eid_key_)>(
  481. qr_secret,
  482. base::span<const uint8_t>(),
  483. device::cablev2::DerivedValueType::kEIDKey)),
  484. peer_identity_(device::fido_parsing_utils::Materialize(peer_identity)),
  485. secret_(fido_parsing_utils::Materialize(qr_secret)) {
  486. websocket_client_ = std::make_unique<device::cablev2::WebSocketAdapter>(
  487. base::BindOnce(&LateLinkingDevice::OnTunnelReady,
  488. base::Unretained(this)),
  489. base::BindRepeating(&LateLinkingDevice::OnTunnelData,
  490. base::Unretained(this)));
  491. const GURL target = device::cablev2::tunnelserver::GetNewTunnelURL(
  492. kTunnelServer, tunnel_id_);
  493. network_context_->CreateWebSocket(
  494. target, {device::kCableWebSocketProtocol}, net::SiteForCookies(),
  495. net::IsolationInfo(), /*additional_headers=*/{},
  496. network::mojom::kBrowserProcessId, url::Origin::Create(target),
  497. network::mojom::kWebSocketOptionBlockAllCookies,
  498. net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS),
  499. websocket_client_->BindNewHandshakeClientPipe(),
  500. /*url_loader_network_observer=*/mojo::NullRemote(),
  501. /*auth_handler=*/mojo::NullRemote(),
  502. /*header_client=*/mojo::NullRemote(),
  503. /*throttling_profile_id=*/absl::nullopt);
  504. }
  505. private:
  506. void OnTunnelReady(
  507. WebSocketAdapter::Result result,
  508. absl::optional<std::array<uint8_t, device::cablev2::kRoutingIdSize>>
  509. routing_id) {
  510. CHECK_EQ(result, WebSocketAdapter::Result::OK);
  511. CHECK(routing_id);
  512. CableEidArray plaintext_eid;
  513. device::cablev2::eid::Components components;
  514. components.tunnel_server_domain = kTunnelServer;
  515. components.routing_id = *routing_id;
  516. components.nonce = RandomNonce();
  517. plaintext_eid = device::cablev2::eid::FromComponents(components);
  518. ble_advert_ =
  519. platform_->SendBLEAdvert(eid::Encrypt(plaintext_eid, eid_key_));
  520. psk_ = device::cablev2::Derive<EXTENT(psk_)>(
  521. secret_, plaintext_eid, device::cablev2::DerivedValueType::kPSK);
  522. }
  523. std::array<uint8_t, device::cablev2::kNonceSize> RandomNonce() {
  524. std::array<uint8_t, device::cablev2::kNonceSize> ret;
  525. crypto::RandBytes(ret);
  526. return ret;
  527. }
  528. // BuildGetInfoResponse returns a CBOR-encoded getInfo response.
  529. std::vector<uint8_t> BuildGetInfoResponse() {
  530. std::array<uint8_t, device::kAaguidLength> aaguid{};
  531. std::vector<cbor::Value> versions;
  532. versions.emplace_back("FIDO_2_0");
  533. versions.emplace_back("FIDO_2_1");
  534. cbor::Value::MapValue options;
  535. options.emplace("uv", true);
  536. options.emplace("rk", true);
  537. cbor::Value::MapValue response_map;
  538. response_map.emplace(1, std::move(versions));
  539. response_map.emplace(3, aaguid);
  540. response_map.emplace(4, std::move(options));
  541. return cbor::Writer::Write(cbor::Value(std::move(response_map))).value();
  542. }
  543. void OnTunnelData(absl::optional<base::span<const uint8_t>> msg) {
  544. if (!msg) {
  545. platform_->OnCompleted(absl::nullopt);
  546. return;
  547. }
  548. switch (state_) {
  549. case State::kWaitingForConnection: {
  550. std::vector<uint8_t> response;
  551. HandshakeResult result = RespondToHandshake(
  552. psk_, /*identity=*/nullptr, peer_identity_, *msg, &response);
  553. CHECK(result);
  554. handshake_hash_ = result->second;
  555. websocket_client_->Write(response);
  556. crypter_ = std::move(result->first);
  557. crypter_->UseNewConstruction();
  558. cbor::Value::MapValue post_handshake_msg;
  559. post_handshake_msg.emplace(1, BuildGetInfoResponse());
  560. absl::optional<std::vector<uint8_t>> post_handshake_msg_bytes =
  561. cbor::Writer::Write(cbor::Value(std::move(post_handshake_msg)));
  562. CHECK(post_handshake_msg_bytes);
  563. CHECK(crypter_->Encrypt(&post_handshake_msg_bytes.value()));
  564. websocket_client_->Write(*post_handshake_msg_bytes);
  565. state_ = State::kWaitingForShutdown;
  566. break;
  567. }
  568. case State::kWaitingForShutdown: {
  569. std::vector<uint8_t> plaintext;
  570. CHECK(crypter_->Decrypt(*msg, &plaintext));
  571. CHECK(!plaintext.empty());
  572. const uint8_t message_type_byte = plaintext[0];
  573. plaintext.erase(plaintext.begin());
  574. CHECK_LE(message_type_byte,
  575. static_cast<uint8_t>(MessageType::kMaxValue));
  576. const MessageType message_type =
  577. static_cast<MessageType>(message_type_byte);
  578. switch (message_type) {
  579. case MessageType::kCTAP: {
  580. std::vector<uint8_t> response = {
  581. message_type_byte,
  582. static_cast<uint8_t>(ctap_error_),
  583. };
  584. CHECK(crypter_->Encrypt(&response));
  585. websocket_client_->Write(response);
  586. break;
  587. }
  588. case MessageType::kShutdown:
  589. state_ = State::kShutdownReceived;
  590. base::SequencedTaskRunnerHandle::Get()->PostDelayedTask(
  591. FROM_HERE,
  592. base::BindOnce(&LateLinkingDevice::SendLinkingUpdate,
  593. base::Unretained(this)),
  594. base::Seconds(10));
  595. break;
  596. case MessageType::kUpdate:
  597. CHECK(false);
  598. break;
  599. }
  600. break;
  601. }
  602. case State::kShutdownReceived:
  603. CHECK(false);
  604. break;
  605. }
  606. }
  607. void SendLinkingUpdate() {
  608. CHECK(state_ == State::kShutdownReceived);
  609. std::vector<uint8_t> contact_id = {1, 2, 3, 4};
  610. std::array<uint8_t, kPairingIDSize> pairing_id = {5, 6, 7, 8};
  611. std::array<uint8_t, 32> paired_secret = {9, 10, 11, 12};
  612. std::array<uint8_t, 32> root_secret = {13, 14, 15, 16};
  613. bssl::UniquePtr<EC_KEY> identity_key(IdentityKey(root_secret));
  614. device::CableAuthenticatorIdentityKey public_key;
  615. CHECK_EQ(
  616. public_key.size(),
  617. EC_POINT_point2oct(EC_KEY_get0_group(identity_key.get()),
  618. EC_KEY_get0_public_key(identity_key.get()),
  619. POINT_CONVERSION_UNCOMPRESSED, public_key.data(),
  620. public_key.size(), /*ctx=*/nullptr));
  621. cbor::Value::MapValue pairing;
  622. pairing.emplace(1, std::move(contact_id));
  623. pairing.emplace(2, pairing_id);
  624. pairing.emplace(3, paired_secret);
  625. pairing.emplace(4, public_key);
  626. pairing.emplace(5, "Device name");
  627. pairing.emplace(
  628. 6, CalculatePairingSignature(identity_key.get(), peer_identity_,
  629. handshake_hash_));
  630. cbor::Value::MapValue update_msg;
  631. update_msg.emplace(1, cbor::Value(std::move(pairing)));
  632. absl::optional<std::vector<uint8_t>> update_msg_bytes =
  633. cbor::Writer::Write(cbor::Value(std::move(update_msg)));
  634. CHECK(update_msg_bytes);
  635. update_msg_bytes->insert(update_msg_bytes->begin(),
  636. static_cast<uint8_t>(MessageType::kUpdate));
  637. CHECK(crypter_->Encrypt(&update_msg_bytes.value()));
  638. websocket_client_->Write(*update_msg_bytes);
  639. }
  640. enum class State {
  641. kWaitingForConnection,
  642. kWaitingForShutdown,
  643. kShutdownReceived,
  644. };
  645. const CtapDeviceResponseCode ctap_error_;
  646. const std::unique_ptr<Platform> platform_;
  647. const raw_ptr<network::mojom::NetworkContext> network_context_;
  648. const std::array<uint8_t, kTunnelIdSize> tunnel_id_;
  649. const std::array<uint8_t, kEIDKeySize> eid_key_;
  650. const std::array<uint8_t, kP256X962Length> peer_identity_;
  651. const std::vector<uint8_t> secret_;
  652. State state_ = State::kWaitingForConnection;
  653. std::unique_ptr<WebSocketAdapter> websocket_client_;
  654. std::unique_ptr<Crypter> crypter_;
  655. std::unique_ptr<Platform::BLEAdvert> ble_advert_;
  656. std::array<uint8_t, kPSKSize> psk_;
  657. GURL target_;
  658. HandshakeHash handshake_hash_;
  659. };
  660. } // namespace
  661. } // namespace authenticator
  662. std::unique_ptr<network::mojom::NetworkContext> NewMockTunnelServer(
  663. absl::optional<ContactCallback> contact_callback) {
  664. return std::make_unique<TestNetworkContext>(std::move(contact_callback));
  665. }
  666. namespace authenticator {
  667. std::unique_ptr<authenticator::Platform> NewMockPlatform(
  668. Discovery::AdvertEventStream::Callback ble_advert_callback,
  669. device::VirtualCtap2Device* ctap2_device,
  670. authenticator::Observer* observer) {
  671. return std::make_unique<TestPlatform>(ble_advert_callback, ctap2_device,
  672. observer);
  673. }
  674. // NewLateLinkingDevice returns a caBLEv2 authenticator that sends linking
  675. // information 10 seconds after the CTAP2 transition is complete. It fails all
  676. // CTAP2 requests with the given error.
  677. std::unique_ptr<Transaction> NewLateLinkingDevice(
  678. CtapDeviceResponseCode ctap_error,
  679. std::unique_ptr<Platform> platform,
  680. network::mojom::NetworkContext* network_context,
  681. base::span<const uint8_t> qr_secret,
  682. base::span<const uint8_t, kP256X962Length> peer_identity) {
  683. return std::make_unique<LateLinkingDevice>(ctap_error, std::move(platform),
  684. network_context, qr_secret,
  685. peer_identity);
  686. }
  687. } // namespace authenticator
  688. } // namespace device::cablev2