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
5 #include "device/fido/device_response_converter.h"
6
7 #include <memory>
8 #include <string>
9 #include <utility>
10
11 #include "base/containers/span.h"
12 #include "base/i18n/streaming_utf8_validator.h"
13 #include "base/numerics/safe_conversions.h"
14 #include "base/optional.h"
15 #include "base/stl_util.h"
16 #include "base/strings/string_number_conversions.h"
17 #include "components/cbor/diagnostic_writer.h"
18 #include "components/cbor/reader.h"
19 #include "components/cbor/writer.h"
20 #include "components/device_event_log/device_event_log.h"
21 #include "device/fido/authenticator_data.h"
22 #include "device/fido/authenticator_supported_options.h"
23 #include "device/fido/client_data.h"
24 #include "device/fido/features.h"
25 #include "device/fido/fido_constants.h"
26 #include "device/fido/opaque_attestation_statement.h"
27
28 namespace device {
29
30 namespace {
31
32 constexpr size_t kResponseCodeLength = 1;
33
ConvertStringToProtocolVersion(base::StringPiece version)34 ProtocolVersion ConvertStringToProtocolVersion(base::StringPiece version) {
35 if (version == kCtap2Version)
36 return ProtocolVersion::kCtap2;
37 if (version == kU2fVersion)
38 return ProtocolVersion::kU2f;
39
40 return ProtocolVersion::kUnknown;
41 }
42
43 // Converts a CBOR unsigned integer value to a uint32_t. The conversion is
44 // clamped at uint32_max.
CBORUnsignedToUint32Safe(const cbor::Value & value)45 uint32_t CBORUnsignedToUint32Safe(const cbor::Value& value) {
46 DCHECK(value.is_unsigned());
47 constexpr uint32_t uint32_max = std::numeric_limits<uint32_t>::max();
48 const int64_t n = value.GetUnsigned();
49 return n > uint32_max ? uint32_max : n;
50 }
51
52 } // namespace
53
54 using CBOR = cbor::Value;
55
GetResponseCode(base::span<const uint8_t> buffer)56 CtapDeviceResponseCode GetResponseCode(base::span<const uint8_t> buffer) {
57 if (buffer.empty())
58 return CtapDeviceResponseCode::kCtap2ErrInvalidCBOR;
59
60 auto code = static_cast<CtapDeviceResponseCode>(buffer[0]);
61 return base::Contains(GetCtapResponseCodeList(), code)
62 ? code
63 : CtapDeviceResponseCode::kCtap2ErrInvalidCBOR;
64 }
65
66 // Decodes byte array response from authenticator to CBOR value object and
67 // checks for correct encoding format.
68 base::Optional<AuthenticatorMakeCredentialResponse>
ReadCTAPMakeCredentialResponse(FidoTransportProtocol transport_used,const base::Optional<cbor::Value> & cbor)69 ReadCTAPMakeCredentialResponse(FidoTransportProtocol transport_used,
70 const base::Optional<cbor::Value>& cbor) {
71 if (!cbor || !cbor->is_map())
72 return base::nullopt;
73
74 const auto& decoded_map = cbor->GetMap();
75 auto it = decoded_map.find(CBOR(1));
76 if (it == decoded_map.end() || !it->second.is_string())
77 return base::nullopt;
78 auto format = it->second.GetString();
79
80 it = decoded_map.find(CBOR(2));
81 if (it == decoded_map.end() || !it->second.is_bytestring())
82 return base::nullopt;
83
84 auto authenticator_data =
85 AuthenticatorData::DecodeAuthenticatorData(it->second.GetBytestring());
86 if (!authenticator_data)
87 return base::nullopt;
88
89 it = decoded_map.find(CBOR(3));
90 if (it == decoded_map.end() || !it->second.is_map())
91 return base::nullopt;
92
93 AuthenticatorMakeCredentialResponse response(
94 transport_used,
95 AttestationObject(std::move(*authenticator_data),
96 std::make_unique<OpaqueAttestationStatement>(
97 format, it->second.Clone())));
98
99 if (base::FeatureList::IsEnabled(kWebAuthPhoneSupport)) {
100 it = decoded_map.find(CBOR(kAndroidClientDataExtOutputKey));
101 if (it != decoded_map.end() && it->second.is_bytestring()) {
102 response.set_android_client_data_ext(it->second.GetBytestring());
103 }
104 }
105
106 return std::move(response);
107 }
108
ReadCTAPGetAssertionResponse(const base::Optional<cbor::Value> & cbor)109 base::Optional<AuthenticatorGetAssertionResponse> ReadCTAPGetAssertionResponse(
110 const base::Optional<cbor::Value>& cbor) {
111 if (!cbor || !cbor->is_map())
112 return base::nullopt;
113
114 auto& response_map = cbor->GetMap();
115
116 auto it = response_map.find(CBOR(2));
117 if (it == response_map.end() || !it->second.is_bytestring())
118 return base::nullopt;
119
120 auto auth_data =
121 AuthenticatorData::DecodeAuthenticatorData(it->second.GetBytestring());
122 if (!auth_data)
123 return base::nullopt;
124
125 it = response_map.find(CBOR(3));
126 if (it == response_map.end() || !it->second.is_bytestring())
127 return base::nullopt;
128
129 auto signature = it->second.GetBytestring();
130 AuthenticatorGetAssertionResponse response(std::move(*auth_data),
131 std::move(signature));
132
133 it = response_map.find(CBOR(1));
134 if (it != response_map.end()) {
135 auto credential =
136 PublicKeyCredentialDescriptor::CreateFromCBORValue(it->second);
137 if (!credential)
138 return base::nullopt;
139 response.SetCredential(std::move(*credential));
140 }
141
142 it = response_map.find(CBOR(4));
143 if (it != response_map.end()) {
144 auto user = PublicKeyCredentialUserEntity::CreateFromCBORValue(it->second);
145 if (!user)
146 return base::nullopt;
147 response.SetUserEntity(std::move(*user));
148 }
149
150 it = response_map.find(CBOR(5));
151 if (it != response_map.end()) {
152 if (!it->second.is_unsigned())
153 return base::nullopt;
154
155 response.SetNumCredentials(it->second.GetUnsigned());
156 }
157
158 if (base::FeatureList::IsEnabled(kWebAuthPhoneSupport)) {
159 it = response_map.find(CBOR(kAndroidClientDataExtOutputKey));
160 if (it != response_map.end() && it->second.is_bytestring()) {
161 response.set_android_client_data_ext(it->second.GetBytestring());
162 }
163 }
164
165 return std::move(response);
166 }
167
ReadCTAPGetInfoResponse(base::span<const uint8_t> buffer)168 base::Optional<AuthenticatorGetInfoResponse> ReadCTAPGetInfoResponse(
169 base::span<const uint8_t> buffer) {
170 if (buffer.size() <= kResponseCodeLength ||
171 GetResponseCode(buffer) != CtapDeviceResponseCode::kSuccess)
172 return base::nullopt;
173
174 cbor::Reader::DecoderError error;
175 base::Optional<CBOR> decoded_response =
176 cbor::Reader::Read(buffer.subspan(1), &error);
177
178 if (!decoded_response) {
179 FIDO_LOG(ERROR) << "-> (CBOR parse error from GetInfo response '"
180 << cbor::Reader::ErrorCodeToString(error)
181 << "' from raw message "
182 << base::HexEncode(buffer.data(), buffer.size()) << ")";
183 return base::nullopt;
184 }
185
186 if (!decoded_response->is_map())
187 return base::nullopt;
188
189 FIDO_LOG(DEBUG) << "-> " << cbor::DiagnosticWriter::Write(*decoded_response);
190 const auto& response_map = decoded_response->GetMap();
191
192 auto it = response_map.find(CBOR(1));
193 if (it == response_map.end() || !it->second.is_array()) {
194 return base::nullopt;
195 }
196
197 base::flat_set<ProtocolVersion> protocol_versions;
198 base::flat_set<base::StringPiece> advertised_protocols;
199 for (const auto& version : it->second.GetArray()) {
200 if (!version.is_string())
201 return base::nullopt;
202 const std::string& version_string = version.GetString();
203
204 if (!advertised_protocols.insert(version_string).second) {
205 // Duplicate versions are not allowed.
206 return base::nullopt;
207 }
208
209 auto protocol = ConvertStringToProtocolVersion(version_string);
210 if (protocol == ProtocolVersion::kUnknown) {
211 FIDO_LOG(DEBUG) << "Unexpected protocol version received.";
212 continue;
213 }
214
215 if (!protocol_versions.insert(protocol).second) {
216 // A duplicate value will have already caused an error therefore hitting
217 // this suggests that |ConvertStringToProtocolVersion| is non-injective.
218 NOTREACHED();
219 return base::nullopt;
220 }
221 }
222
223 if (protocol_versions.empty())
224 return base::nullopt;
225
226 it = response_map.find(CBOR(3));
227 if (it == response_map.end() || !it->second.is_bytestring() ||
228 it->second.GetBytestring().size() != kAaguidLength) {
229 return base::nullopt;
230 }
231
232 AuthenticatorGetInfoResponse response(
233 std::move(protocol_versions),
234 base::make_span<kAaguidLength>(it->second.GetBytestring()));
235
236 AuthenticatorSupportedOptions options;
237 it = response_map.find(CBOR(2));
238 if (it != response_map.end()) {
239 if (!it->second.is_array())
240 return base::nullopt;
241
242 std::vector<std::string> extensions;
243 for (const auto& extension : it->second.GetArray()) {
244 if (!extension.is_string())
245 return base::nullopt;
246
247 const std::string& extension_str = extension.GetString();
248 if (extension_str == kExtensionCredProtect) {
249 options.supports_cred_protect = true;
250 } else if (extension_str == kExtensionAndroidClientData) {
251 options.supports_android_client_data_ext = true;
252 }
253 extensions.push_back(extension_str);
254 }
255 response.extensions = std::move(extensions);
256 }
257
258 it = response_map.find(CBOR(4));
259 if (it != response_map.end()) {
260 if (!it->second.is_map())
261 return base::nullopt;
262
263 const auto& option_map = it->second.GetMap();
264 auto option_map_it = option_map.find(CBOR(kPlatformDeviceMapKey));
265 if (option_map_it != option_map.end()) {
266 if (!option_map_it->second.is_bool())
267 return base::nullopt;
268
269 options.is_platform_device = option_map_it->second.GetBool();
270 }
271
272 option_map_it = option_map.find(CBOR(kResidentKeyMapKey));
273 if (option_map_it != option_map.end()) {
274 if (!option_map_it->second.is_bool())
275 return base::nullopt;
276
277 options.supports_resident_key = option_map_it->second.GetBool();
278 }
279
280 option_map_it = option_map.find(CBOR(kUserPresenceMapKey));
281 if (option_map_it != option_map.end()) {
282 if (!option_map_it->second.is_bool())
283 return base::nullopt;
284
285 options.supports_user_presence = option_map_it->second.GetBool();
286 }
287
288 option_map_it = option_map.find(CBOR(kUserVerificationMapKey));
289 if (option_map_it != option_map.end()) {
290 if (!option_map_it->second.is_bool())
291 return base::nullopt;
292
293 if (option_map_it->second.GetBool()) {
294 options.user_verification_availability = AuthenticatorSupportedOptions::
295 UserVerificationAvailability::kSupportedAndConfigured;
296 } else {
297 options.user_verification_availability = AuthenticatorSupportedOptions::
298 UserVerificationAvailability::kSupportedButNotConfigured;
299 }
300 }
301
302 option_map_it = option_map.find(CBOR(kClientPinMapKey));
303 if (option_map_it != option_map.end()) {
304 if (!option_map_it->second.is_bool())
305 return base::nullopt;
306
307 if (option_map_it->second.GetBool()) {
308 options.client_pin_availability = AuthenticatorSupportedOptions::
309 ClientPinAvailability::kSupportedAndPinSet;
310 } else {
311 options.client_pin_availability = AuthenticatorSupportedOptions::
312 ClientPinAvailability::kSupportedButPinNotSet;
313 }
314 }
315
316 option_map_it = option_map.find(CBOR(kCredentialManagementMapKey));
317 if (option_map_it != option_map.end()) {
318 if (!option_map_it->second.is_bool()) {
319 return base::nullopt;
320 }
321 options.supports_credential_management = option_map_it->second.GetBool();
322 }
323
324 option_map_it = option_map.find(CBOR(kCredentialManagementPreviewMapKey));
325 if (option_map_it != option_map.end()) {
326 if (!option_map_it->second.is_bool()) {
327 return base::nullopt;
328 }
329 options.supports_credential_management_preview =
330 option_map_it->second.GetBool();
331 }
332
333 option_map_it = option_map.find(CBOR(kBioEnrollmentMapKey));
334 if (option_map_it != option_map.end()) {
335 if (!option_map_it->second.is_bool()) {
336 return base::nullopt;
337 }
338 using Availability =
339 AuthenticatorSupportedOptions::BioEnrollmentAvailability;
340
341 options.bio_enrollment_availability =
342 option_map_it->second.GetBool()
343 ? Availability::kSupportedAndProvisioned
344 : Availability::kSupportedButUnprovisioned;
345 }
346
347 option_map_it = option_map.find(CBOR(kBioEnrollmentPreviewMapKey));
348 if (option_map_it != option_map.end()) {
349 if (!option_map_it->second.is_bool()) {
350 return base::nullopt;
351 }
352 using Availability =
353 AuthenticatorSupportedOptions::BioEnrollmentAvailability;
354
355 options.bio_enrollment_availability_preview =
356 option_map_it->second.GetBool()
357 ? Availability::kSupportedAndProvisioned
358 : Availability::kSupportedButUnprovisioned;
359 }
360
361 option_map_it = option_map.find(CBOR(kUvTokenMapKey));
362 if (option_map_it != option_map.end()) {
363 if (!option_map_it->second.is_bool()) {
364 return base::nullopt;
365 }
366 options.supports_uv_token = option_map_it->second.GetBool();
367 }
368
369 response.options = std::move(options);
370 }
371
372 it = response_map.find(CBOR(5));
373 if (it != response_map.end()) {
374 if (!it->second.is_unsigned())
375 return base::nullopt;
376
377 response.max_msg_size = CBORUnsignedToUint32Safe(it->second);
378 }
379
380 it = response_map.find(CBOR(6));
381 if (it != response_map.end()) {
382 if (!it->second.is_array())
383 return base::nullopt;
384
385 std::vector<uint8_t> supported_pin_protocols;
386 for (const auto& protocol : it->second.GetArray()) {
387 if (!protocol.is_unsigned())
388 return base::nullopt;
389
390 supported_pin_protocols.push_back(protocol.GetUnsigned());
391 }
392 response.pin_protocols = std::move(supported_pin_protocols);
393 }
394
395 it = response_map.find(CBOR(7));
396 if (it != response_map.end()) {
397 if (!it->second.is_unsigned())
398 return base::nullopt;
399
400 response.max_credential_count_in_list =
401 CBORUnsignedToUint32Safe(it->second);
402 }
403
404 it = response_map.find(CBOR(8));
405 if (it != response_map.end()) {
406 if (!it->second.is_unsigned())
407 return base::nullopt;
408
409 response.max_credential_id_length = CBORUnsignedToUint32Safe(it->second);
410 }
411
412 return base::Optional<AuthenticatorGetInfoResponse>(std::move(response));
413 }
414
FixInvalidUTF8String(base::span<const uint8_t> utf8_bytes)415 static base::Optional<std::string> FixInvalidUTF8String(
416 base::span<const uint8_t> utf8_bytes) {
417 // CTAP2 devices must store at least 64 bytes of any string.
418 if (utf8_bytes.size() < 64) {
419 FIDO_LOG(ERROR) << "Not accepting invalid UTF-8 string because it's only "
420 << utf8_bytes.size() << " bytes long";
421 return base::nullopt;
422 }
423
424 base::StreamingUtf8Validator validator;
425 base::StreamingUtf8Validator::State state;
426 size_t longest_valid_prefix_len = 0;
427
428 for (size_t i = 0; i < utf8_bytes.size(); i++) {
429 state =
430 validator.AddBytes(reinterpret_cast<const char*>(&utf8_bytes[i]), 1);
431 switch (state) {
432 case base::StreamingUtf8Validator::VALID_ENDPOINT:
433 longest_valid_prefix_len = i + 1;
434 break;
435
436 case base::StreamingUtf8Validator::INVALID:
437 return base::nullopt;
438
439 case base::StreamingUtf8Validator::VALID_MIDPOINT:
440 break;
441 }
442 }
443
444 switch (state) {
445 case base::StreamingUtf8Validator::VALID_ENDPOINT:
446 // |base::IsStringUTF8|, which the CBOR code uses, is stricter than
447 // |StreamingUtf8Validator| in that the former rejects ranges of code
448 // points that should never appear. Therefore, if this case occurs, the
449 // string is structurally valid as UTF-8, but includes invalid code points
450 // and thus we reject it.
451 return base::nullopt;
452
453 case base::StreamingUtf8Validator::INVALID:
454 // This shouldn't happen because we should return immediately if
455 // |INVALID| occurs.
456 NOTREACHED();
457 return base::nullopt;
458
459 case base::StreamingUtf8Validator::VALID_MIDPOINT: {
460 // This string has been truncated. This is the case that we expect to
461 // have to handle since CTAP2 devices are permitted to truncate strings
462 // without reference to UTF-8.
463 const std::string candidate(
464 reinterpret_cast<const char*>(utf8_bytes.data()),
465 longest_valid_prefix_len);
466 // Check that the result is now acceptable to |IsStringUTF8|, which is
467 // stricter than |StreamingUtf8Validator|. Without this, a string could
468 // have both contained invalid code-points /and/ been truncated, and this
469 // function would only have corrected the latter issue.
470 if (base::IsStringUTF8(candidate)) {
471 return candidate;
472 }
473 return base::nullopt;
474 }
475 }
476 }
477
478 typedef bool (*PathPredicate)(const std::vector<const cbor::Value*>&);
479
FixInvalidUTF8Value(const cbor::Value & v,std::vector<const cbor::Value * > * path,PathPredicate predicate)480 static base::Optional<cbor::Value> FixInvalidUTF8Value(
481 const cbor::Value& v,
482 std::vector<const cbor::Value*>* path,
483 PathPredicate predicate) {
484 switch (v.type()) {
485 case cbor::Value::Type::INVALID_UTF8: {
486 if (!predicate(*path)) {
487 return base::nullopt;
488 }
489 base::Optional<std::string> maybe_fixed(
490 FixInvalidUTF8String(v.GetInvalidUTF8()));
491 if (!maybe_fixed) {
492 return base::nullopt;
493 }
494 return cbor::Value(*maybe_fixed);
495 }
496
497 case cbor::Value::Type::UNSIGNED:
498 case cbor::Value::Type::NEGATIVE:
499 case cbor::Value::Type::BYTE_STRING:
500 case cbor::Value::Type::STRING:
501 case cbor::Value::Type::TAG:
502 case cbor::Value::Type::SIMPLE_VALUE:
503 case cbor::Value::Type::NONE:
504 return v.Clone();
505
506 case cbor::Value::Type::ARRAY: {
507 const cbor::Value::ArrayValue& old_array = v.GetArray();
508 cbor::Value::ArrayValue new_array;
509 new_array.reserve(old_array.size());
510
511 for (const auto& child : old_array) {
512 base::Optional<cbor::Value> maybe_fixed =
513 FixInvalidUTF8Value(child, path, predicate);
514 if (!maybe_fixed) {
515 return base::nullopt;
516 }
517 new_array.emplace_back(std::move(*maybe_fixed));
518 }
519
520 return cbor::Value(new_array);
521 }
522
523 case cbor::Value::Type::MAP: {
524 const cbor::Value::MapValue& old_map = v.GetMap();
525 cbor::Value::MapValue new_map;
526 new_map.reserve(old_map.size());
527
528 for (const auto& it : old_map) {
529 switch (it.first.type()) {
530 case cbor::Value::Type::INVALID_UTF8:
531 // Invalid strings in map keys are not supported.
532 return base::nullopt;
533
534 case cbor::Value::Type::UNSIGNED:
535 case cbor::Value::Type::NEGATIVE:
536 case cbor::Value::Type::STRING:
537 break;
538
539 default:
540 // Other types are not permitted as map keys in CTAP2.
541 return base::nullopt;
542 }
543
544 path->push_back(&it.first);
545 base::Optional<cbor::Value> maybe_fixed =
546 FixInvalidUTF8Value(it.second, path, predicate);
547 path->pop_back();
548 if (!maybe_fixed) {
549 return base::nullopt;
550 }
551
552 new_map.emplace(it.first.Clone(), std::move(*maybe_fixed));
553 }
554
555 return cbor::Value(new_map);
556 }
557 }
558 }
559
560 // ContainsInvalidUTF8 returns true if any element of |v| (recursively) contains
561 // a string with invalid UTF-8. It bases this determination purely on the type
562 // of the nodes and doesn't actually check the contents of the strings
563 // themselves.
ContainsInvalidUTF8(const cbor::Value & v)564 static bool ContainsInvalidUTF8(const cbor::Value& v) {
565 switch (v.type()) {
566 case cbor::Value::Type::INVALID_UTF8:
567 return true;
568
569 case cbor::Value::Type::UNSIGNED:
570 case cbor::Value::Type::NEGATIVE:
571 case cbor::Value::Type::BYTE_STRING:
572 case cbor::Value::Type::STRING:
573 case cbor::Value::Type::TAG:
574 case cbor::Value::Type::SIMPLE_VALUE:
575 case cbor::Value::Type::NONE:
576 return false;
577
578 case cbor::Value::Type::ARRAY: {
579 const cbor::Value::ArrayValue& array = v.GetArray();
580 for (const auto& child : array) {
581 if (ContainsInvalidUTF8(child)) {
582 return true;
583 }
584 }
585
586 return false;
587 }
588
589 case cbor::Value::Type::MAP: {
590 const cbor::Value::MapValue& map = v.GetMap();
591 for (const auto& it : map) {
592 if (ContainsInvalidUTF8(it.first) || ContainsInvalidUTF8(it.second)) {
593 return true;
594 }
595 }
596
597 return false;
598 }
599 }
600 }
601
FixInvalidUTF8(cbor::Value in,PathPredicate predicate)602 base::Optional<cbor::Value> FixInvalidUTF8(cbor::Value in,
603 PathPredicate predicate) {
604 if (!ContainsInvalidUTF8(in)) {
605 // Common case that everything is fine.
606 return std::move(in);
607 }
608
609 std::vector<const cbor::Value*> path;
610 return FixInvalidUTF8Value(in, &path, predicate);
611 }
612
613 } // namespace device
614