fido_tunnel_device.cc 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733
  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/fido_tunnel_device.h"
  5. #include "base/metrics/histogram_functions.h"
  6. #include "base/strings/string_number_conversions.h"
  7. #include "base/threading/thread_task_runner_handle.h"
  8. #include "components/cbor/reader.h"
  9. #include "components/cbor/values.h"
  10. #include "components/cbor/writer.h"
  11. #include "components/device_event_log/device_event_log.h"
  12. #include "crypto/random.h"
  13. #include "device/fido/cable/cable_discovery_data.h"
  14. #include "device/fido/cbor_extract.h"
  15. #include "device/fido/fido_constants.h"
  16. #include "device/fido/fido_parsing_utils.h"
  17. #include "net/traffic_annotation/network_traffic_annotation.h"
  18. #include "third_party/boringssl/src/include/openssl/aes.h"
  19. #include "third_party/boringssl/src/include/openssl/digest.h"
  20. #include "third_party/boringssl/src/include/openssl/hkdf.h"
  21. using device::cbor_extract::IntKey;
  22. using device::cbor_extract::Is;
  23. using device::cbor_extract::StepOrByte;
  24. using device::cbor_extract::Stop;
  25. namespace device {
  26. namespace cablev2 {
  27. namespace {
  28. // CableV2TunnelEvent enumerates several steps that occur during establishing a
  29. // caBLEv2 tunnel. Do not change the assigned values since they are used in
  30. // histograms, only append new values. Keep synced with enums.xml.
  31. enum class CableV2TunnelEvent {
  32. kStartedKeyed = 0,
  33. kStartedLinked = 1,
  34. kTunnelOk = 2,
  35. kTunnelGone = 3,
  36. kTunnelFailed410 = 4,
  37. kTunnelFailed = 5,
  38. kHandshakeFailed = 6,
  39. kPostHandshakeFailed = 7,
  40. kTunnelEstablished = 8,
  41. kDecryptFailed = 9,
  42. kMaxValue = 9,
  43. };
  44. void RecordEvent(CableV2TunnelEvent event) {
  45. base::UmaHistogramEnumeration("WebAuthentication.CableV2.TunnelEvent", event);
  46. }
  47. std::array<uint8_t, 8> RandomId() {
  48. std::array<uint8_t, 8> ret;
  49. crypto::RandBytes(ret);
  50. return ret;
  51. }
  52. } // namespace
  53. FidoTunnelDevice::QRInfo::QRInfo() = default;
  54. FidoTunnelDevice::QRInfo::~QRInfo() = default;
  55. FidoTunnelDevice::PairedInfo::PairedInfo() = default;
  56. FidoTunnelDevice::PairedInfo::~PairedInfo() = default;
  57. constexpr net::NetworkTrafficAnnotationTag kTrafficAnnotation =
  58. net::DefineNetworkTrafficAnnotation("cablev2_websocket_from_client", R"(
  59. semantics {
  60. sender: "Phone as a Security Key"
  61. description:
  62. "Chrome can communicate with a phone for the purpose of using "
  63. "the phone as a security key. This WebSocket connection is made to "
  64. "a rendezvous service of the phone's choosing. Mostly likely that "
  65. "is a Google service because the phone-side is being handled by "
  66. "Chrome on that device. The service carries only end-to-end "
  67. "encrypted data where the keys are shared directly between the "
  68. "client and phone via QR code and Bluetooth broadcast."
  69. trigger:
  70. "A web-site initiates a WebAuthn request and the user scans a QR "
  71. "code with their phone."
  72. data: "Only encrypted data that the service does not have the keys "
  73. "for."
  74. destination: GOOGLE_OWNED_SERVICE
  75. }
  76. policy {
  77. cookies_allowed: NO
  78. setting: "Not controlled by a setting because the operation is "
  79. "triggered by significant user action."
  80. policy_exception_justification:
  81. "No policy provided because the operation is triggered by "
  82. " significant user action. No background activity occurs."
  83. })");
  84. FidoTunnelDevice::FidoTunnelDevice(
  85. network::mojom::NetworkContext* network_context,
  86. absl::optional<base::RepeatingCallback<void(std::unique_ptr<Pairing>)>>
  87. pairing_callback,
  88. base::span<const uint8_t> secret,
  89. base::span<const uint8_t, kQRSeedSize> local_identity_seed,
  90. const CableEidArray& decrypted_eid)
  91. : info_(absl::in_place_type<QRInfo>), id_(RandomId()) {
  92. const eid::Components components = eid::ToComponents(decrypted_eid);
  93. QRInfo& info = absl::get<QRInfo>(info_);
  94. info.pairing_callback = std::move(pairing_callback);
  95. info.local_identity_seed =
  96. fido_parsing_utils::Materialize(local_identity_seed);
  97. info.tunnel_server_domain = components.tunnel_server_domain;
  98. info.psk =
  99. Derive<EXTENT(info.psk)>(secret, decrypted_eid, DerivedValueType::kPSK);
  100. std::array<uint8_t, 16> tunnel_id;
  101. tunnel_id = Derive<EXTENT(tunnel_id)>(secret, base::span<uint8_t>(),
  102. DerivedValueType::kTunnelID);
  103. const GURL url(tunnelserver::GetConnectURL(components.tunnel_server_domain,
  104. components.routing_id, tunnel_id));
  105. FIDO_LOG(DEBUG) << GetId() << ": connecting caBLEv2 tunnel: " << url;
  106. RecordEvent(CableV2TunnelEvent::kStartedKeyed);
  107. websocket_client_ = std::make_unique<device::cablev2::WebSocketAdapter>(
  108. base::BindOnce(&FidoTunnelDevice::OnTunnelReady, base::Unretained(this)),
  109. base::BindRepeating(&FidoTunnelDevice::OnTunnelData,
  110. base::Unretained(this)));
  111. network_context->CreateWebSocket(
  112. url, {kCableWebSocketProtocol}, net::SiteForCookies(),
  113. net::IsolationInfo(), /*additional_headers=*/{},
  114. network::mojom::kBrowserProcessId, url::Origin::Create(url),
  115. network::mojom::kWebSocketOptionBlockAllCookies,
  116. net::MutableNetworkTrafficAnnotationTag(kTrafficAnnotation),
  117. websocket_client_->BindNewHandshakeClientPipe(),
  118. /*url_loader_network_observer=*/mojo::NullRemote(),
  119. /*auth_handler=*/mojo::NullRemote(),
  120. /*header_client=*/mojo::NullRemote(),
  121. /*throttling_profile_id=*/absl::nullopt);
  122. }
  123. FidoTunnelDevice::FidoTunnelDevice(
  124. FidoRequestType request_type,
  125. network::mojom::NetworkContext* network_context,
  126. std::unique_ptr<Pairing> pairing,
  127. base::OnceClosure pairing_is_invalid)
  128. : info_(absl::in_place_type<PairedInfo>), id_(RandomId()) {
  129. uint8_t client_nonce[kClientNonceSize];
  130. crypto::RandBytes(client_nonce);
  131. cbor::Value::MapValue client_payload;
  132. client_payload.emplace(1, pairing->id);
  133. client_payload.emplace(2, base::span<const uint8_t>(client_nonce));
  134. client_payload.emplace(3, RequestTypeToString(request_type));
  135. const absl::optional<std::vector<uint8_t>> client_payload_bytes =
  136. cbor::Writer::Write(cbor::Value(std::move(client_payload)));
  137. CHECK(client_payload_bytes.has_value());
  138. const std::string client_payload_hex = base::HexEncode(*client_payload_bytes);
  139. PairedInfo& info = absl::get<PairedInfo>(info_);
  140. info.eid_encryption_key = Derive<EXTENT(info.eid_encryption_key)>(
  141. pairing->secret, client_nonce, DerivedValueType::kEIDKey);
  142. info.peer_identity = pairing->peer_public_key_x962;
  143. info.secret = pairing->secret;
  144. info.pairing_is_invalid = std::move(pairing_is_invalid);
  145. const GURL url = tunnelserver::GetContactURL(pairing->tunnel_server_domain,
  146. pairing->contact_id);
  147. FIDO_LOG(DEBUG) << GetId() << ": connecting caBLEv2 tunnel: " << url;
  148. RecordEvent(CableV2TunnelEvent::kStartedLinked);
  149. websocket_client_ = std::make_unique<device::cablev2::WebSocketAdapter>(
  150. base::BindOnce(&FidoTunnelDevice::OnTunnelReady, base::Unretained(this)),
  151. base::BindRepeating(&FidoTunnelDevice::OnTunnelData,
  152. base::Unretained(this)));
  153. std::vector<network::mojom::HttpHeaderPtr> headers;
  154. headers.emplace_back(network::mojom::HttpHeader::New(
  155. kCableClientPayloadHeader, client_payload_hex));
  156. network_context->CreateWebSocket(
  157. url, {kCableWebSocketProtocol}, net::SiteForCookies(),
  158. net::IsolationInfo(), std::move(headers),
  159. network::mojom::kBrowserProcessId, url::Origin::Create(url),
  160. network::mojom::kWebSocketOptionBlockAllCookies,
  161. net::MutableNetworkTrafficAnnotationTag(kTrafficAnnotation),
  162. websocket_client_->BindNewHandshakeClientPipe(),
  163. /*url_loader_network_observer=*/mojo::NullRemote(),
  164. /*auth_handler=*/mojo::NullRemote(),
  165. /*header_client=*/mojo::NullRemote(),
  166. /*throttling_profile_id=*/absl::nullopt);
  167. }
  168. FidoTunnelDevice::~FidoTunnelDevice() {
  169. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  170. if (state_ == State::kReady) {
  171. established_connection_->Close();
  172. }
  173. }
  174. bool FidoTunnelDevice::MatchAdvert(
  175. const std::array<uint8_t, kAdvertSize>& advert) {
  176. PairedInfo& info = absl::get<PairedInfo>(info_);
  177. absl::optional<CableEidArray> plaintext =
  178. eid::Decrypt(advert, info.eid_encryption_key);
  179. if (!plaintext) {
  180. return false;
  181. }
  182. info.psk = Derive<EXTENT(*info.psk)>(info.secret, *plaintext,
  183. DerivedValueType::kPSK);
  184. if (state_ == State::kWaitingForEID) {
  185. // We were waiting for this BLE advert in order to start the handshake.
  186. DCHECK(!handshake_);
  187. handshake_.emplace(*info.psk, info.peer_identity,
  188. /*local_identity=*/absl::nullopt);
  189. websocket_client_->Write(handshake_->BuildInitialMessage());
  190. state_ = State::kHandshakeSent;
  191. }
  192. return true;
  193. }
  194. FidoDevice::CancelToken FidoTunnelDevice::DeviceTransact(
  195. std::vector<uint8_t> command,
  196. DeviceCallback callback) {
  197. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  198. if (state_ == State::kError) {
  199. base::SequencedTaskRunnerHandle::Get()->PostTask(
  200. FROM_HERE, base::BindOnce(std::move(callback), absl::nullopt));
  201. } else if (state_ != State::kReady) {
  202. DCHECK(!pending_callback_);
  203. pending_message_ = std::move(command);
  204. pending_callback_ = std::move(callback);
  205. } else {
  206. DeviceTransactReady(std::move(command), std::move(callback));
  207. }
  208. // TODO: cancelation would be useful, but it depends on the GMSCore action
  209. // being cancelable on Android, which it currently is not.
  210. return kInvalidCancelToken + 1;
  211. }
  212. void FidoTunnelDevice::Cancel(CancelToken token) {
  213. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  214. }
  215. std::string FidoTunnelDevice::GetId() const {
  216. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  217. return "tunnel-" + base::HexEncode(id_);
  218. }
  219. FidoTransportProtocol FidoTunnelDevice::DeviceTransport() const {
  220. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  221. return FidoTransportProtocol::kHybrid;
  222. }
  223. base::WeakPtr<FidoDevice> FidoTunnelDevice::GetWeakPtr() {
  224. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  225. return weak_factory_.GetWeakPtr();
  226. }
  227. void FidoTunnelDevice::OnTunnelReady(
  228. WebSocketAdapter::Result result,
  229. absl::optional<std::array<uint8_t, kRoutingIdSize>> routing_id) {
  230. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  231. DCHECK_EQ(State::kConnecting, state_);
  232. switch (result) {
  233. case WebSocketAdapter::Result::OK:
  234. DCHECK(!handshake_);
  235. RecordEvent(CableV2TunnelEvent::kTunnelOk);
  236. if (auto* info = absl::get_if<QRInfo>(&info_)) {
  237. // A QR handshake can start as soon as the tunnel is connected.
  238. handshake_.emplace(info->psk, /*peer_identity=*/absl::nullopt,
  239. info->local_identity_seed);
  240. } else {
  241. // A paired handshake may be able to start if we have already seen
  242. // the BLE advert.
  243. PairedInfo& paired_info = absl::get<PairedInfo>(info_);
  244. if (paired_info.psk) {
  245. handshake_.emplace(*paired_info.psk, paired_info.peer_identity,
  246. /*local_identity=*/absl::nullopt);
  247. }
  248. }
  249. if (handshake_) {
  250. websocket_client_->Write(handshake_->BuildInitialMessage());
  251. state_ = State::kHandshakeSent;
  252. } else {
  253. state_ = State::kWaitingForEID;
  254. }
  255. break;
  256. case WebSocketAdapter::Result::GONE:
  257. if (auto* info = absl::get_if<PairedInfo>(&info_)) {
  258. FIDO_LOG(DEBUG) << GetId()
  259. << ": tunnel server reports that contact ID is invalid";
  260. RecordEvent(CableV2TunnelEvent::kTunnelGone);
  261. std::move(info->pairing_is_invalid).Run();
  262. } else {
  263. FIDO_LOG(ERROR) << GetId()
  264. << ": server reported an invalid contact ID for an "
  265. "unpaired connection";
  266. RecordEvent(CableV2TunnelEvent::kTunnelFailed410);
  267. }
  268. [[fallthrough]];
  269. case WebSocketAdapter::Result::FAILED:
  270. RecordEvent(CableV2TunnelEvent::kTunnelFailed);
  271. FIDO_LOG(DEBUG) << GetId() << ": tunnel failed to connect";
  272. OnError();
  273. break;
  274. }
  275. }
  276. void FidoTunnelDevice::OnTunnelData(
  277. absl::optional<base::span<const uint8_t>> data) {
  278. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  279. if (!data) {
  280. OnError();
  281. return;
  282. }
  283. switch (state_) {
  284. case State::kError:
  285. break;
  286. case State::kConnecting:
  287. case State::kWaitingForEID:
  288. OnError();
  289. break;
  290. case State::kHandshakeSent: {
  291. // This is the handshake response.
  292. HandshakeResult result = handshake_->ProcessResponse(*data);
  293. handshake_.reset();
  294. if (!result) {
  295. FIDO_LOG(ERROR) << GetId() << ": caBLEv2 handshake failed";
  296. RecordEvent(CableV2TunnelEvent::kHandshakeFailed);
  297. OnError();
  298. return;
  299. }
  300. crypter_ = std::move(result->first);
  301. handshake_hash_ = result->second;
  302. state_ = State::kWaitingForPostHandshakeMessage;
  303. break;
  304. }
  305. case State::kWaitingForPostHandshakeMessage: {
  306. // This is the post-handshake message that contains the getInfo response
  307. // and, optionally, linking information.
  308. std::vector<uint8_t> decrypted;
  309. if (!crypter_->Decrypt(*data, &decrypted)) {
  310. FIDO_LOG(ERROR)
  311. << GetId()
  312. << ": decryption failed for caBLE post-handshake message";
  313. RecordEvent(CableV2TunnelEvent::kPostHandshakeFailed);
  314. OnError();
  315. return;
  316. }
  317. int protocol_revision = 1;
  318. absl::optional<cbor::Value> payload = cbor::Reader::Read(decrypted);
  319. if (!payload) {
  320. // This message was padded in revision zero, which will cause a parse
  321. // error from `cbor::Reader::Read`.
  322. protocol_revision = 0;
  323. payload = DecodePaddedCBORMap(decrypted);
  324. }
  325. if (!payload || !payload->is_map()) {
  326. FIDO_LOG(ERROR) << GetId()
  327. << ": decode failed for caBLE post-handshake message";
  328. RecordEvent(CableV2TunnelEvent::kPostHandshakeFailed);
  329. OnError();
  330. return;
  331. }
  332. const cbor::Value::MapValue& map = payload->GetMap();
  333. const cbor::Value::MapValue::const_iterator getinfo_it =
  334. map.find(cbor::Value(1));
  335. if (getinfo_it == map.end() || !getinfo_it->second.is_bytestring()) {
  336. FIDO_LOG(ERROR)
  337. << GetId()
  338. << ": caBLE post-handshake message missing getInfo response";
  339. RecordEvent(CableV2TunnelEvent::kPostHandshakeFailed);
  340. OnError();
  341. return;
  342. }
  343. getinfo_response_bytes_ = getinfo_it->second.GetBytestring();
  344. // Linking information is always optional. Currently it is ignored outside
  345. // of a QR handshake but, in future, we may need to be able to update
  346. // linking information.
  347. const cbor::Value::MapValue::const_iterator linking_it =
  348. map.find(cbor::Value(2));
  349. if (linking_it != map.end()) {
  350. if (!linking_it->second.is_map()) {
  351. FIDO_LOG(ERROR)
  352. << GetId()
  353. << ": invalid linking data in caBLE post-handshake message";
  354. RecordEvent(CableV2TunnelEvent::kPostHandshakeFailed);
  355. OnError();
  356. return;
  357. }
  358. if (auto* info = absl::get_if<QRInfo>(&info_)) {
  359. absl::optional<std::unique_ptr<Pairing>> maybe_pairing =
  360. Pairing::Parse(linking_it->second, info->tunnel_server_domain,
  361. info->local_identity_seed, *handshake_hash_);
  362. if (!maybe_pairing) {
  363. FIDO_LOG(ERROR)
  364. << GetId()
  365. << ": invalid linking data in caBLE post-handshake message";
  366. RecordEvent(CableV2TunnelEvent::kPostHandshakeFailed);
  367. OnError();
  368. return;
  369. }
  370. FIDO_LOG(DEBUG) << "Linking information processed from caBLE device";
  371. if (info->pairing_callback) {
  372. info->pairing_callback->Run(std::move(*maybe_pairing));
  373. }
  374. }
  375. } else {
  376. FIDO_LOG(DEBUG)
  377. << "Linking information was not received from caBLE device";
  378. }
  379. FIDO_LOG(DEBUG) << GetId() << ": established v2." << protocol_revision;
  380. RecordEvent(CableV2TunnelEvent::kTunnelEstablished);
  381. state_ = State::kReady;
  382. established_connection_ = base::MakeRefCounted<EstablishedConnection>(
  383. std::move(websocket_client_), GetId(), protocol_revision,
  384. std::move(crypter_), *handshake_hash_, absl::get_if<QRInfo>(&info_));
  385. if (pending_callback_) {
  386. DeviceTransactReady(std::move(pending_message_),
  387. std::move(pending_callback_));
  388. }
  389. break;
  390. }
  391. case State::kReady: {
  392. // In |kReady| the connection is handled by |established_connection_| and
  393. // so this should never happen.
  394. NOTREACHED();
  395. break;
  396. }
  397. }
  398. }
  399. void FidoTunnelDevice::OnError() {
  400. const State previous_state = state_;
  401. state_ = State::kError;
  402. if (previous_state == State::kReady) {
  403. DCHECK(!pending_callback_);
  404. DCHECK(!websocket_client_);
  405. established_connection_->Close();
  406. established_connection_.reset();
  407. } else {
  408. websocket_client_.reset();
  409. if (pending_callback_) {
  410. std::move(pending_callback_).Run(absl::nullopt);
  411. }
  412. }
  413. }
  414. void FidoTunnelDevice::DeviceTransactReady(std::vector<uint8_t> command,
  415. DeviceCallback callback) {
  416. DCHECK_EQ(state_, State::kReady);
  417. if (command.size() != 1 ||
  418. command[0] !=
  419. static_cast<uint8_t>(CtapRequestCommand::kAuthenticatorGetInfo)) {
  420. established_connection_->Transact(std::move(command), std::move(callback));
  421. return;
  422. }
  423. DCHECK(!getinfo_response_bytes_.empty());
  424. std::vector<uint8_t> reply;
  425. reply.reserve(1 + getinfo_response_bytes_.size());
  426. reply.push_back(static_cast<uint8_t>(CtapDeviceResponseCode::kSuccess));
  427. reply.insert(reply.end(), getinfo_response_bytes_.begin(),
  428. getinfo_response_bytes_.end());
  429. base::SequencedTaskRunnerHandle::Get()->PostTask(
  430. FROM_HERE, base::BindOnce(std::move(callback), std::move(reply)));
  431. }
  432. // g_num_established_connection_instances is incremented when an
  433. // `EstablishedConnection` is created and decremented during its destructor.
  434. // This is purely for checking that none leak in tests.
  435. static int g_num_established_connection_instances;
  436. int FidoTunnelDevice::GetNumEstablishedConnectionInstancesForTesting() {
  437. return g_num_established_connection_instances;
  438. }
  439. FidoTunnelDevice::EstablishedConnection::EstablishedConnection(
  440. std::unique_ptr<WebSocketAdapter> websocket_client,
  441. std::string id_for_logging,
  442. int protocol_revision,
  443. std::unique_ptr<Crypter> crypter,
  444. const HandshakeHash& handshake_hash,
  445. QRInfo* maybe_qr_info)
  446. : self_reference_(this),
  447. websocket_client_(std::move(websocket_client)),
  448. id_for_logging_(std::move(id_for_logging)),
  449. protocol_revision_(protocol_revision),
  450. crypter_(std::move(crypter)),
  451. handshake_hash_(handshake_hash) {
  452. g_num_established_connection_instances++;
  453. websocket_client_->Reparent(base::BindRepeating(
  454. &EstablishedConnection::OnTunnelData, base::Unretained(this)));
  455. if (maybe_qr_info) {
  456. pairing_callback_ = maybe_qr_info->pairing_callback;
  457. local_identity_seed_ = maybe_qr_info->local_identity_seed;
  458. tunnel_server_domain_ = maybe_qr_info->tunnel_server_domain;
  459. }
  460. }
  461. FidoTunnelDevice::EstablishedConnection::~EstablishedConnection() {
  462. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  463. g_num_established_connection_instances--;
  464. }
  465. void FidoTunnelDevice::EstablishedConnection::Transact(
  466. std::vector<uint8_t> message,
  467. DeviceCallback callback) {
  468. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  469. DCHECK(state_ == State::kRunning || state_ == State::kRemoteShutdown);
  470. if (protocol_revision_ >= 1) {
  471. message.insert(message.begin(), static_cast<uint8_t>(MessageType::kCTAP));
  472. }
  473. if (state_ == State::kRemoteShutdown || !crypter_->Encrypt(&message)) {
  474. base::SequencedTaskRunnerHandle::Get()->PostTask(
  475. FROM_HERE, base::BindOnce(std::move(callback), absl::nullopt));
  476. return;
  477. }
  478. DCHECK(!callback_);
  479. callback_ = std::move(callback);
  480. websocket_client_->Write(message);
  481. }
  482. void FidoTunnelDevice::EstablishedConnection::Close() {
  483. switch (state_) {
  484. case State::kRunning: {
  485. if (protocol_revision_ < 1) {
  486. OnRemoteClose();
  487. DCHECK_EQ(state_, State::kRemoteShutdown);
  488. Close();
  489. return;
  490. }
  491. callback_.Reset();
  492. state_ = State::kLocallyShutdown;
  493. std::vector<uint8_t> shutdown_msg = {
  494. static_cast<uint8_t>(MessageType::kShutdown)};
  495. if (crypter_->Encrypt(&shutdown_msg)) {
  496. websocket_client_->Write(shutdown_msg);
  497. }
  498. timer_.Start(FROM_HERE, base::Minutes(3), this,
  499. &EstablishedConnection::OnTimeout);
  500. break;
  501. }
  502. case State::kRemoteShutdown:
  503. state_ = State::kClosed;
  504. self_reference_.reset();
  505. // `this` may be invalid now.
  506. return;
  507. case State::kLocallyShutdown:
  508. case State::kClosed:
  509. NOTREACHED();
  510. }
  511. }
  512. void FidoTunnelDevice::EstablishedConnection::OnTunnelData(
  513. absl::optional<base::span<const uint8_t>> data) {
  514. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  515. DCHECK(state_ == State::kRunning || state_ == State::kLocallyShutdown);
  516. if (!data) {
  517. OnRemoteClose();
  518. // `this` may be invalid now.
  519. return;
  520. }
  521. std::vector<uint8_t> plaintext;
  522. if (!crypter_->Decrypt(*data, &plaintext)) {
  523. FIDO_LOG(ERROR) << id_for_logging_
  524. << ": decryption failed for caBLE message";
  525. RecordEvent(CableV2TunnelEvent::kDecryptFailed);
  526. OnRemoteClose();
  527. // `this` may be invalid now.
  528. return;
  529. }
  530. if (protocol_revision_ >= 1) {
  531. if (plaintext.empty()) {
  532. FIDO_LOG(ERROR) << id_for_logging_ << ": invalid empty message";
  533. OnRemoteClose();
  534. // `this` may be invalid now.
  535. return;
  536. }
  537. const uint8_t message_type_byte = plaintext[0];
  538. plaintext.erase(plaintext.begin());
  539. if (message_type_byte > static_cast<uint8_t>(MessageType::kMaxValue)) {
  540. FIDO_LOG(ERROR) << id_for_logging_ << ": invalid message type "
  541. << static_cast<int>(message_type_byte);
  542. OnRemoteClose();
  543. // `this` may be invalid now.
  544. return;
  545. }
  546. const MessageType message_type =
  547. static_cast<MessageType>(message_type_byte);
  548. switch (message_type) {
  549. case MessageType::kShutdown:
  550. // Authenticators don't send shutdown alerts, only clients.
  551. FIDO_LOG(ERROR) << id_for_logging_ << ": invalid shutdown frame";
  552. OnRemoteClose();
  553. // `this` may be invalid now.
  554. return;
  555. case MessageType::kCTAP:
  556. break;
  557. case MessageType::kUpdate: {
  558. if (!ProcessUpdate(plaintext)) {
  559. FIDO_LOG(ERROR) << id_for_logging_ << ": invalid update frame";
  560. OnRemoteClose();
  561. // `this` may be invalid now.
  562. return;
  563. }
  564. return;
  565. }
  566. }
  567. }
  568. if (!callback_) {
  569. if (state_ == State::kLocallyShutdown) {
  570. // If locally shutdown then `callback_` will have been erased this so
  571. // might have been a valid reply.
  572. return;
  573. }
  574. FIDO_LOG(ERROR) << id_for_logging_
  575. << ": unsolicited CTAP message from caBLE device";
  576. OnRemoteClose();
  577. // `this` may be invalid now.
  578. return;
  579. }
  580. std::move(callback_).Run(std::move(plaintext));
  581. }
  582. bool FidoTunnelDevice::EstablishedConnection::ProcessUpdate(
  583. base::span<const uint8_t> plaintext) {
  584. absl::optional<cbor::Value> payload = cbor::Reader::Read(plaintext);
  585. if (!payload || !payload->is_map()) {
  586. return false;
  587. }
  588. const cbor::Value::MapValue& map = payload->GetMap();
  589. const cbor::Value::MapValue::const_iterator linking_it =
  590. map.find(cbor::Value(1));
  591. if (linking_it != map.end()) {
  592. if (!linking_it->second.is_map()) {
  593. return false;
  594. }
  595. if (!pairing_callback_) {
  596. FIDO_LOG(DEBUG) << id_for_logging_
  597. << ": unexpected linking information was discarded";
  598. return true;
  599. }
  600. absl::optional<std::unique_ptr<Pairing>> maybe_pairing =
  601. Pairing::Parse(linking_it->second, *tunnel_server_domain_,
  602. *local_identity_seed_, handshake_hash_);
  603. if (!maybe_pairing) {
  604. return false;
  605. }
  606. FIDO_LOG(DEBUG) << id_for_logging_ << ": received linking information";
  607. pairing_callback_->Run(std::move(*maybe_pairing));
  608. }
  609. return true;
  610. }
  611. void FidoTunnelDevice::EstablishedConnection::OnRemoteClose() {
  612. websocket_client_.reset();
  613. switch (state_) {
  614. case State::kRunning:
  615. state_ = State::kRemoteShutdown;
  616. if (callback_) {
  617. std::move(callback_).Run(absl::nullopt);
  618. }
  619. break;
  620. case State::kLocallyShutdown:
  621. state_ = State::kClosed;
  622. self_reference_.reset();
  623. // `this` may be invalid now.
  624. return;
  625. case State::kRemoteShutdown:
  626. case State::kClosed:
  627. NOTREACHED();
  628. break;
  629. }
  630. }
  631. void FidoTunnelDevice::EstablishedConnection::OnTimeout() {
  632. DCHECK(state_ == State::kLocallyShutdown);
  633. FIDO_LOG(DEBUG) << id_for_logging_ << ": closing connection due to timeout";
  634. OnRemoteClose();
  635. }
  636. } // namespace cablev2
  637. } // namespace device