ctap2_device_operation.h 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. // Copyright 2018 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. #ifndef DEVICE_FIDO_CTAP2_DEVICE_OPERATION_H_
  5. #define DEVICE_FIDO_CTAP2_DEVICE_OPERATION_H_
  6. #include <stdint.h>
  7. #include <string>
  8. #include <utility>
  9. #include <vector>
  10. #include "base/bind.h"
  11. #include "base/callback.h"
  12. #include "base/containers/span.h"
  13. #include "base/memory/weak_ptr.h"
  14. #include "base/strings/string_number_conversions.h"
  15. #include "components/cbor/diagnostic_writer.h"
  16. #include "components/cbor/reader.h"
  17. #include "components/cbor/values.h"
  18. #include "components/cbor/writer.h"
  19. #include "components/device_event_log/device_event_log.h"
  20. #include "device/fido/device_operation.h"
  21. #include "device/fido/device_response_converter.h"
  22. #include "device/fido/fido_constants.h"
  23. #include "device/fido/fido_device.h"
  24. #include "third_party/abseil-cpp/absl/types/optional.h"
  25. namespace device {
  26. // Ctap2DeviceOperation performs a single request--response operation on a CTAP2
  27. // device. The |Request| class must implement a static |EncodeToCBOR| method
  28. // that returns a pair of |CtapRequestCommand| and an optional CBOR |Value|. The
  29. // response will be parsed to CBOR and then further parsed into a |Response|
  30. // using a provided callback.
  31. template <class Request, class Response>
  32. class Ctap2DeviceOperation : public DeviceOperation<Request, Response> {
  33. public:
  34. // DeviceResponseCallback is either called with a |kSuccess| and a |Response|
  35. // object, or else is called with a value other than |kSuccess| and
  36. // |nullopt|.
  37. using DeviceResponseCallback =
  38. base::OnceCallback<void(CtapDeviceResponseCode,
  39. absl::optional<Response>)>;
  40. // DeviceResponseParser converts a generic CBOR structure into an
  41. // operation-specific response. If the response didn't have a payload then the
  42. // argument will be |nullopt|. The parser should return |nullopt| on error.
  43. using DeviceResponseParser = base::OnceCallback<absl::optional<Response>(
  44. const absl::optional<cbor::Value>&)>;
  45. // CBORPathPredicate takes a vector of CBOR |Value|s that are map keys and
  46. // returns true if the string at that location may validly be truncated.
  47. // For example, the path of the string "bar" in {"x": {"y": "foo",
  48. // "z": "bar"}} is ["x", "z"].
  49. //
  50. // It's a function pointer rather than a callback to emphasise that the result
  51. // should be stateless and based only on the static structure of the expected
  52. // message.
  53. typedef bool (*CBORPathPredicate)(
  54. const std::vector<const cbor::Value*>& path);
  55. Ctap2DeviceOperation(FidoDevice* device,
  56. Request request,
  57. DeviceResponseCallback callback,
  58. DeviceResponseParser device_response_parser,
  59. CBORPathPredicate string_fixup_predicate)
  60. : DeviceOperation<Request, Response>(device,
  61. std::move(request),
  62. std::move(callback)),
  63. device_response_parser_(std::move(device_response_parser)),
  64. string_fixup_predicate_(string_fixup_predicate) {}
  65. Ctap2DeviceOperation(const Ctap2DeviceOperation&) = delete;
  66. Ctap2DeviceOperation& operator=(const Ctap2DeviceOperation&) = delete;
  67. ~Ctap2DeviceOperation() override = default;
  68. void Start() override {
  69. std::pair<CtapRequestCommand, absl::optional<cbor::Value>> request(
  70. AsCTAPRequestValuePair(this->request()));
  71. std::vector<uint8_t> request_bytes;
  72. // TODO: it would be nice to see which device each request is going to, but
  73. // that breaks every mock test because they aren't expecting a call to
  74. // GetId().
  75. if (request.second) {
  76. FIDO_LOG(DEBUG) << "<- " << static_cast<int>(request.first) << " "
  77. << cbor::DiagnosticWriter::Write(*request.second);
  78. absl::optional<std::vector<uint8_t>> cbor_bytes =
  79. cbor::Writer::Write(*request.second);
  80. DCHECK(cbor_bytes);
  81. request_bytes = std::move(*cbor_bytes);
  82. } else {
  83. FIDO_LOG(DEBUG) << "<- " << static_cast<int>(request.first)
  84. << " (no payload)";
  85. }
  86. request_bytes.insert(request_bytes.begin(),
  87. static_cast<uint8_t>(request.first));
  88. this->token_ = this->device()->DeviceTransact(
  89. std::move(request_bytes),
  90. base::BindOnce(&Ctap2DeviceOperation::OnResponseReceived,
  91. weak_factory_.GetWeakPtr()));
  92. }
  93. // Cancel requests that the operation be canceled. This is safe to call at any
  94. // time but may not be effective because the operation may have already
  95. // completed or the device may not support cancelation. Even if canceled, the
  96. // callback will still be invoked, albeit perhaps with a status of
  97. // |kCtap2ErrKeepAliveCancel|.
  98. void Cancel() override {
  99. if (this->token_) {
  100. FIDO_LOG(DEBUG) << "<- (cancel)";
  101. this->device()->Cancel(*this->token_);
  102. this->token_.reset();
  103. }
  104. }
  105. void OnResponseReceived(
  106. absl::optional<std::vector<uint8_t>> device_response) {
  107. this->token_.reset();
  108. // TODO: it would be nice to see which device each response is coming from,
  109. // but that breaks every mock test because they aren't expecting a call to
  110. // GetId().
  111. if (!device_response || device_response->empty()) {
  112. FIDO_LOG(ERROR) << "-> (error reading)";
  113. std::move(this->callback())
  114. .Run(CtapDeviceResponseCode::kCtap2ErrOther, absl::nullopt);
  115. return;
  116. }
  117. auto response_code = GetResponseCode(*device_response);
  118. if (response_code != CtapDeviceResponseCode::kSuccess) {
  119. FIDO_LOG(DEBUG) << "-> (CTAP2 error code " << +device_response->at(0)
  120. << ")";
  121. std::move(this->callback()).Run(response_code, absl::nullopt);
  122. return;
  123. }
  124. DCHECK(!device_response->empty());
  125. absl::optional<cbor::Value> cbor;
  126. absl::optional<Response> response;
  127. base::span<const uint8_t> cbor_bytes(*device_response);
  128. cbor_bytes = cbor_bytes.subspan(1);
  129. if (!cbor_bytes.empty()) {
  130. cbor::Reader::DecoderError error;
  131. cbor::Reader::Config config;
  132. config.error_code_out = &error;
  133. if (string_fixup_predicate_) {
  134. config.allow_invalid_utf8 = true;
  135. }
  136. cbor = cbor::Reader::Read(cbor_bytes, config);
  137. if (!cbor) {
  138. FIDO_LOG(ERROR) << "-> (CBOR parse error '"
  139. << cbor::Reader::ErrorCodeToString(error)
  140. << "' from raw message "
  141. << base::HexEncode(device_response->data(),
  142. device_response->size())
  143. << ")";
  144. std::move(this->callback())
  145. .Run(CtapDeviceResponseCode::kCtap2ErrInvalidCBOR, absl::nullopt);
  146. return;
  147. }
  148. if (string_fixup_predicate_) {
  149. cbor = FixInvalidUTF8(std::move(*cbor), string_fixup_predicate_);
  150. if (!cbor) {
  151. FIDO_LOG(ERROR)
  152. << "-> (CBOR with unfixable UTF-8 errors from raw message "
  153. << base::HexEncode(device_response->data(),
  154. device_response->size())
  155. << ")";
  156. std::move(this->callback())
  157. .Run(CtapDeviceResponseCode::kCtap2ErrInvalidCBOR, absl::nullopt);
  158. return;
  159. }
  160. }
  161. response = std::move(std::move(device_response_parser_).Run(cbor));
  162. if (response) {
  163. FIDO_LOG(DEBUG) << "-> " << cbor::DiagnosticWriter::Write(*cbor);
  164. } else {
  165. FIDO_LOG(ERROR) << "-> (rejected CBOR structure) "
  166. << cbor::DiagnosticWriter::Write(*cbor);
  167. }
  168. } else {
  169. response =
  170. std::move(std::move(device_response_parser_).Run(absl::nullopt));
  171. if (response) {
  172. FIDO_LOG(DEBUG) << "-> (empty payload)";
  173. } else {
  174. FIDO_LOG(ERROR) << "-> (rejected empty payload)";
  175. }
  176. }
  177. if (!response) {
  178. response_code = CtapDeviceResponseCode::kCtap2ErrInvalidCBOR;
  179. }
  180. std::move(this->callback()).Run(response_code, std::move(response));
  181. }
  182. private:
  183. DeviceResponseParser device_response_parser_;
  184. const CBORPathPredicate string_fixup_predicate_;
  185. base::WeakPtrFactory<Ctap2DeviceOperation> weak_factory_{this};
  186. };
  187. } // namespace device
  188. #endif // DEVICE_FIDO_CTAP2_DEVICE_OPERATION_H_