make_credential_task.cc 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412
  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. #include "device/fido/make_credential_task.h"
  5. #include <algorithm>
  6. #include <utility>
  7. #include "base/bind.h"
  8. #include "base/containers/contains.h"
  9. #include "device/base/features.h"
  10. #include "device/fido/ctap2_device_operation.h"
  11. #include "device/fido/ctap_make_credential_request.h"
  12. #include "device/fido/pin.h"
  13. #include "device/fido/u2f_command_constructor.h"
  14. #include "device/fido/u2f_register_operation.h"
  15. namespace device {
  16. namespace {
  17. // CTAP 2.0 specifies[1] that once a PIN has been set on an authenticator, the
  18. // PIN is required in order to make a credential. In some cases we don't want to
  19. // prompt for a PIN and so use U2F to make the credential instead.
  20. //
  21. // [1]
  22. // https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#authenticatorMakeCredential,
  23. // step 6
  24. bool CtapDeviceShouldUseU2fBecauseClientPinIsSet(
  25. const FidoDevice* device,
  26. const CtapMakeCredentialRequest& request) {
  27. if (!IsConvertibleToU2fRegisterCommand(request) ||
  28. ShouldPreferCTAP2EvenIfItNeedsAPIN(request)) {
  29. return false;
  30. }
  31. DCHECK_EQ(device->supported_protocol(), ProtocolVersion::kCtap2);
  32. // No need to fall back to U2F if CTAP2 registrations don't require UV.
  33. if (device->device_info()->options.make_cred_uv_not_required) {
  34. return false;
  35. }
  36. // Don't use U2F for requests that require UV or PIN which U2F doesn't
  37. // support. Note that |pin_auth| may also be set by GetTouchRequest(), but we
  38. // don't want those requests to use U2F either if CTAP is supported.
  39. if (request.user_verification == UserVerificationRequirement::kRequired ||
  40. request.pin_auth) {
  41. return false;
  42. }
  43. DCHECK(device && device->device_info());
  44. bool client_pin_set =
  45. device->device_info()->options.client_pin_availability ==
  46. AuthenticatorSupportedOptions::ClientPinAvailability::kSupportedAndPinSet;
  47. bool supports_u2f =
  48. base::Contains(device->device_info()->versions, ProtocolVersion::kU2f);
  49. return client_pin_set && supports_u2f;
  50. }
  51. // ConvertCTAPResponse returns the AuthenticatorMakeCredentialResponse for a
  52. // given CTAP response message in |cbor|. It wraps
  53. // ReadCTAPMakeCredentialResponse() and in addition fills in |is_resident_key|,
  54. // which requires looking at the request and device.
  55. absl::optional<AuthenticatorMakeCredentialResponse> ConvertCTAPResponse(
  56. FidoDevice* device,
  57. bool resident_key_required,
  58. const absl::optional<cbor::Value>& cbor) {
  59. DCHECK_EQ(device->supported_protocol(), ProtocolVersion::kCtap2);
  60. DCHECK(device->device_info());
  61. absl::optional<AuthenticatorMakeCredentialResponse> response =
  62. ReadCTAPMakeCredentialResponse(device->DeviceTransport(), cbor);
  63. if (!response) {
  64. return absl::nullopt;
  65. }
  66. // Fill in whether the created credential is client-side discoverable
  67. // (resident). CTAP 2.0 authenticators may decide to treat all credentials as
  68. // discoverable, so we need to omit the value unless a resident key was
  69. // required.
  70. DCHECK(!response->is_resident_key.has_value());
  71. if (resident_key_required) {
  72. response->is_resident_key = true;
  73. } else {
  74. const bool resident_key_supported =
  75. device->device_info()->options.supports_resident_key;
  76. const base::flat_set<Ctap2Version>& ctap2_versions =
  77. device->device_info()->ctap2_versions;
  78. DCHECK(!ctap2_versions.empty());
  79. const bool is_at_least_ctap2_1 =
  80. std::any_of(ctap2_versions.begin(), ctap2_versions.end(),
  81. [](Ctap2Version v) { return v > Ctap2Version::kCtap2_0; });
  82. if (!resident_key_supported || is_at_least_ctap2_1) {
  83. response->is_resident_key = false;
  84. }
  85. }
  86. if (device->device_info() && device->device_info()->transports) {
  87. response->transports = *device->device_info()->transports;
  88. }
  89. return response;
  90. }
  91. } // namespace
  92. MakeCredentialTask::MakeCredentialTask(FidoDevice* device,
  93. CtapMakeCredentialRequest request,
  94. MakeCredentialOptions options,
  95. MakeCredentialTaskCallback callback)
  96. : FidoTask(device),
  97. request_(std::move(request)),
  98. options_(std::move(options)),
  99. callback_(std::move(callback)) {
  100. // The UV parameter should have been made binary by this point because CTAP2
  101. // only takes a binary value.
  102. DCHECK_NE(request_.user_verification,
  103. UserVerificationRequirement::kPreferred);
  104. }
  105. MakeCredentialTask::~MakeCredentialTask() = default;
  106. // static
  107. CtapMakeCredentialRequest MakeCredentialTask::GetTouchRequest(
  108. const FidoDevice* device) {
  109. // We want to flash and wait for a touch. Newer versions of the CTAP2 spec
  110. // include a provision for blocking for a touch when an empty pinAuth is
  111. // specified, but devices exist that predate this part of the spec and also
  112. // the spec says that devices need only do that if they implement PIN support.
  113. // Therefore, in order to portably wait for a touch, a dummy credential is
  114. // created. This does assume that the device supports ECDSA P-256, however.
  115. PublicKeyCredentialUserEntity user({1} /* user ID */);
  116. // The user name is incorrectly marked as optional in the CTAP2 spec.
  117. user.name = "dummy";
  118. CtapMakeCredentialRequest req(
  119. "" /* client_data_json */, PublicKeyCredentialRpEntity(kDummyRpID),
  120. std::move(user),
  121. PublicKeyCredentialParams(
  122. {{CredentialType::kPublicKey,
  123. base::strict_cast<int>(CoseAlgorithmIdentifier::kEs256)}}));
  124. // If a device supports CTAP2 and has PIN support then setting an empty
  125. // pinAuth should trigger just a touch[1]. Our U2F code also understands
  126. // this convention.
  127. // [1]
  128. // https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#using-pinToken-in-authenticatorGetAssertion
  129. if (device->supported_protocol() == ProtocolVersion::kU2f ||
  130. (device->device_info() &&
  131. device->device_info()->options.client_pin_availability !=
  132. AuthenticatorSupportedOptions::ClientPinAvailability::
  133. kNotSupported)) {
  134. req.pin_auth.emplace();
  135. req.pin_protocol = PINUVAuthProtocol::kV1;
  136. }
  137. DCHECK(IsConvertibleToU2fRegisterCommand(req));
  138. return req;
  139. }
  140. // static
  141. bool MakeCredentialTask::WillUseCTAP2(const FidoDevice* device,
  142. const CtapMakeCredentialRequest& request,
  143. const MakeCredentialOptions& options) {
  144. return device->supported_protocol() == ProtocolVersion::kCtap2 &&
  145. !options.make_u2f_api_credential &&
  146. !CtapDeviceShouldUseU2fBecauseClientPinIsSet(device, request);
  147. }
  148. void MakeCredentialTask::Cancel() {
  149. canceled_ = true;
  150. if (register_operation_) {
  151. register_operation_->Cancel();
  152. }
  153. if (silent_sign_operation_) {
  154. silent_sign_operation_->Cancel();
  155. }
  156. }
  157. void MakeCredentialTask::StartTask() {
  158. if (WillUseCTAP2(device(), request_, options_)) {
  159. MakeCredential();
  160. } else {
  161. // |device_info| should be present iff the device is CTAP2. This will be
  162. // used in |MaybeRevertU2fFallback| to restore the protocol of CTAP2 devices
  163. // once this task is complete.
  164. DCHECK_EQ(device()->supported_protocol() == ProtocolVersion::kCtap2,
  165. device()->device_info().has_value());
  166. device()->set_supported_protocol(ProtocolVersion::kU2f);
  167. U2fRegister();
  168. }
  169. }
  170. CtapGetAssertionRequest MakeCredentialTask::NextSilentRequest() {
  171. DCHECK(current_exclude_list_batch_ < exclude_list_batches_.size());
  172. CtapGetAssertionRequest request(request_.rp.id,
  173. /*client_data_json=*/"");
  174. request.allow_list = exclude_list_batches_.at(current_exclude_list_batch_);
  175. request.user_presence_required = false;
  176. request.user_verification = UserVerificationRequirement::kDiscouraged;
  177. // If a pinUvAuthToken was obtained for the original request, the silent
  178. // requests should carry one as well. This is to ensure that excluded
  179. // credentials with credProtect-level uvRequired can be matched.
  180. DCHECK_EQ(request_.pin_auth.has_value(),
  181. request_.pin_token_for_exclude_list_probing.has_value());
  182. if (request_.pin_token_for_exclude_list_probing) {
  183. std::tie(request.pin_protocol, request.pin_auth) =
  184. request_.pin_token_for_exclude_list_probing->PinAuth(
  185. request.client_data_hash);
  186. }
  187. return request;
  188. }
  189. void MakeCredentialTask::MakeCredential() {
  190. DCHECK_EQ(device()->supported_protocol(), ProtocolVersion::kCtap2);
  191. // Most authenticators can only process excludeList parameters up to a certain
  192. // size. Batch the list into chunks according to what the device can handle
  193. // and filter out IDs that are too large to originate from this device.
  194. exclude_list_batches_ =
  195. FilterAndBatchCredentialDescriptors(request_.exclude_list, *device());
  196. DCHECK(!exclude_list_batches_.empty());
  197. // If the filtered excludeList is small enough to be sent in a single request,
  198. // do so. (Note that the exclude list may be empty now, even if it wasn't
  199. // previously, due to filtering.)
  200. if (exclude_list_batches_.size() == 1 || device()->NoSilentRequests()) {
  201. auto request = request_;
  202. request.exclude_list = exclude_list_batches_.front();
  203. register_operation_ = std::make_unique<Ctap2DeviceOperation<
  204. CtapMakeCredentialRequest, AuthenticatorMakeCredentialResponse>>(
  205. device(), std::move(request), std::move(callback_),
  206. base::BindOnce(&ConvertCTAPResponse, device(),
  207. request_.resident_key_required),
  208. /*string_fixup_predicate=*/nullptr);
  209. register_operation_->Start();
  210. return;
  211. }
  212. // If the filtered list is too large to be sent at once then probe the
  213. // credential IDs silently.
  214. silent_sign_operation_ =
  215. std::make_unique<Ctap2DeviceOperation<CtapGetAssertionRequest,
  216. AuthenticatorGetAssertionResponse>>(
  217. device(), NextSilentRequest(),
  218. base::BindOnce(&MakeCredentialTask::HandleResponseToSilentSignRequest,
  219. weak_factory_.GetWeakPtr()),
  220. base::BindOnce(&ReadCTAPGetAssertionResponse,
  221. device()->DeviceTransport()),
  222. /*string_fixup_predicate=*/nullptr);
  223. silent_sign_operation_->Start();
  224. }
  225. void MakeCredentialTask::HandleResponseToSilentSignRequest(
  226. CtapDeviceResponseCode response_code,
  227. absl::optional<AuthenticatorGetAssertionResponse> response_data) {
  228. if (canceled_) {
  229. return;
  230. }
  231. // The authenticator recognized a credential from previous exclude list batch.
  232. // Send the actual request with only that exclude list batch to collect a
  233. // touch and and the CTAP2_ERR_CREDENTIAL_EXCLUDED error code.
  234. if (response_code == CtapDeviceResponseCode::kSuccess) {
  235. CtapMakeCredentialRequest request = request_;
  236. request.exclude_list =
  237. exclude_list_batches_.at(current_exclude_list_batch_);
  238. register_operation_ = std::make_unique<Ctap2DeviceOperation<
  239. CtapMakeCredentialRequest, AuthenticatorMakeCredentialResponse>>(
  240. device(), std::move(request), std::move(callback_),
  241. base::BindOnce(&ConvertCTAPResponse, device(),
  242. request_.resident_key_required),
  243. /*string_fixup_predicate=*/nullptr);
  244. register_operation_->Start();
  245. return;
  246. }
  247. // The authenticator returned an unexpected error. Collect a touch to take the
  248. // authenticator out of the set of active devices.
  249. if (!FidoDevice::IsStatusForUnrecognisedCredentialID(response_code)) {
  250. register_operation_ = std::make_unique<Ctap2DeviceOperation<
  251. CtapMakeCredentialRequest, AuthenticatorMakeCredentialResponse>>(
  252. device(), GetTouchRequest(device()),
  253. base::BindOnce(&MakeCredentialTask::HandleResponseToDummyTouch,
  254. weak_factory_.GetWeakPtr()),
  255. base::BindOnce(&ConvertCTAPResponse, device(),
  256. /*resident_key_required=*/false),
  257. /*string_fixup_predicate=*/nullptr);
  258. register_operation_->Start();
  259. return;
  260. }
  261. // The authenticator didn't recognize any credential from the previous exclude
  262. // list batch. Try the next batch, if there is one.
  263. current_exclude_list_batch_++;
  264. if (current_exclude_list_batch_ < exclude_list_batches_.size()) {
  265. silent_sign_operation_ = std::make_unique<Ctap2DeviceOperation<
  266. CtapGetAssertionRequest, AuthenticatorGetAssertionResponse>>(
  267. device(), NextSilentRequest(),
  268. base::BindOnce(&MakeCredentialTask::HandleResponseToSilentSignRequest,
  269. weak_factory_.GetWeakPtr()),
  270. base::BindOnce(&ReadCTAPGetAssertionResponse,
  271. device()->DeviceTransport()),
  272. /*string_fixup_predicate=*/nullptr);
  273. silent_sign_operation_->Start();
  274. return;
  275. }
  276. // None of the credentials from the exclude list were recognized. The actual
  277. // register request may proceed but without the exclude list present in case
  278. // it exceeds the device's size limit.
  279. CtapMakeCredentialRequest request = request_;
  280. request.exclude_list = {};
  281. register_operation_ = std::make_unique<Ctap2DeviceOperation<
  282. CtapMakeCredentialRequest, AuthenticatorMakeCredentialResponse>>(
  283. device(), std::move(request), std::move(callback_),
  284. base::BindOnce(&ConvertCTAPResponse, device(),
  285. request_.resident_key_required),
  286. /*string_fixup_predicate=*/nullptr);
  287. register_operation_->Start();
  288. }
  289. void MakeCredentialTask::HandleResponseToDummyTouch(
  290. CtapDeviceResponseCode response_code,
  291. absl::optional<AuthenticatorMakeCredentialResponse> response_data) {
  292. std::move(callback_).Run(CtapDeviceResponseCode::kCtap2ErrOther,
  293. absl::nullopt);
  294. }
  295. void MakeCredentialTask::U2fRegister() {
  296. if (!IsConvertibleToU2fRegisterCommand(request_)) {
  297. std::move(callback_).Run(CtapDeviceResponseCode::kCtap2ErrOther,
  298. absl::nullopt);
  299. return;
  300. }
  301. DCHECK_EQ(ProtocolVersion::kU2f, device()->supported_protocol());
  302. register_operation_ = std::make_unique<U2fRegisterOperation>(
  303. device(), std::move(request_),
  304. base::BindOnce(&MakeCredentialTask::MaybeRevertU2fFallback,
  305. weak_factory_.GetWeakPtr()));
  306. register_operation_->Start();
  307. }
  308. void MakeCredentialTask::MaybeRevertU2fFallback(
  309. CtapDeviceResponseCode status,
  310. absl::optional<AuthenticatorMakeCredentialResponse> response) {
  311. DCHECK_EQ(ProtocolVersion::kU2f, device()->supported_protocol());
  312. if (device()->device_info()) {
  313. // This was actually a CTAP2 device, but the protocol version was set to U2F
  314. // because it had a PIN set and so, in order to make a credential, the U2F
  315. // interface was used.
  316. device()->set_supported_protocol(ProtocolVersion::kCtap2);
  317. }
  318. DCHECK(!response || *response->is_resident_key == false);
  319. std::move(callback_).Run(status, std::move(response));
  320. }
  321. std::vector<std::vector<PublicKeyCredentialDescriptor>>
  322. FilterAndBatchCredentialDescriptors(
  323. const std::vector<PublicKeyCredentialDescriptor>& in,
  324. const FidoDevice& device) {
  325. DCHECK_EQ(device.supported_protocol(), ProtocolVersion::kCtap2);
  326. DCHECK(device.device_info().has_value());
  327. if (device.NoSilentRequests()) {
  328. // caBLE devices might not support silent probing, so just put everything
  329. // into one batch that can will be sent in a non-probing request.
  330. return {in};
  331. }
  332. const auto& device_info = *device.device_info();
  333. // Note that |max_credential_id_length| of 0 is interpreted as unbounded.
  334. size_t max_credential_id_length =
  335. device_info.max_credential_id_length.value_or(0);
  336. // Protect against devices that claim to have a maximum list length of 0, or
  337. // to know the maximum list length but not know the maximum size of an
  338. // individual credential ID.
  339. size_t max_credential_count_in_list =
  340. max_credential_id_length > 0
  341. ? std::max(device_info.max_credential_count_in_list.value_or(1), 1u)
  342. : 1;
  343. std::vector<std::vector<PublicKeyCredentialDescriptor>> result;
  344. result.emplace_back();
  345. for (const PublicKeyCredentialDescriptor& credential : in) {
  346. if (0 < max_credential_id_length &&
  347. max_credential_id_length < credential.id.size()) {
  348. continue;
  349. }
  350. if (result.back().size() == max_credential_count_in_list) {
  351. result.emplace_back();
  352. }
  353. result.back().push_back(credential);
  354. }
  355. return result;
  356. }
  357. } // namespace device