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