| // Copyright 2016 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/usb/usb_blocklist.h" |
| |
| #include <algorithm> |
| #include <string> |
| #include <tuple> |
| |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_split.h" |
| #include "components/variations/variations_associated_data.h" |
| #include "device/usb/public/mojom/device.mojom.h" |
| |
| namespace { |
| |
| static base::LazyInstance<UsbBlocklist>::Leaky g_singleton = |
| LAZY_INSTANCE_INITIALIZER; |
| |
| constexpr uint16_t kMaxVersion = 0xffff; |
| |
| // Returns true if the passed string is exactly 4 digits long and only contains |
| // valid hexadecimal characters (no leading 0x). |
| bool IsHexComponent(base::StringPiece string) { |
| if (string.length() != 4) |
| return false; |
| |
| // This is necessary because base::HexStringToUInt allows whitespace and the |
| // "0x" prefix in its input. |
| for (char c : string) { |
| if (c >= '0' && c <= '9') |
| continue; |
| if (c >= 'a' && c <= 'f') |
| continue; |
| if (c >= 'A' && c <= 'F') |
| continue; |
| return false; |
| } |
| return true; |
| } |
| |
| bool CompareEntry(const UsbBlocklist::Entry& a, const UsbBlocklist::Entry& b) { |
| return std::tie(a.vendor_id, a.product_id, a.max_version) < |
| std::tie(b.vendor_id, b.product_id, b.max_version); |
| } |
| |
| // Returns true if an entry in (begin, end] matches the vendor and product IDs |
| // of |entry| and has a device version greater than or equal to |entry|. |
| template <class Iterator> |
| bool EntryMatches(Iterator begin, |
| Iterator end, |
| const UsbBlocklist::Entry& entry) { |
| auto it = std::lower_bound(begin, end, entry, CompareEntry); |
| return it != end && it->vendor_id == entry.vendor_id && |
| it->product_id == entry.product_id; |
| } |
| |
| // This list must be sorted according to CompareEntry. |
| const UsbBlocklist::Entry kStaticEntries[] = { |
| {0x096e, 0x0850, kMaxVersion}, // KEY-ID |
| {0x096e, 0x0852, kMaxVersion}, // Feitian |
| {0x096e, 0x0853, kMaxVersion}, // Feitian |
| {0x096e, 0x0854, kMaxVersion}, // Feitian |
| {0x096e, 0x0856, kMaxVersion}, // Feitian |
| {0x096e, 0x0858, kMaxVersion}, // Feitian USB+NFC |
| {0x096e, 0x085a, kMaxVersion}, // Feitian |
| {0x096e, 0x085b, kMaxVersion}, // Feitian |
| {0x096e, 0x0880, kMaxVersion}, // HyperFIDO |
| |
| {0x09c3, 0x0023, kMaxVersion}, // HID Global BlueTrust Token |
| |
| // Yubikey devices. https://crbug.com/818807 |
| {0x1050, 0x0010, kMaxVersion}, |
| {0x1050, 0x0018, kMaxVersion}, |
| {0x1050, 0x0030, kMaxVersion}, |
| {0x1050, 0x0110, kMaxVersion}, |
| {0x1050, 0x0111, kMaxVersion}, |
| {0x1050, 0x0112, kMaxVersion}, |
| {0x1050, 0x0113, kMaxVersion}, |
| {0x1050, 0x0114, kMaxVersion}, |
| {0x1050, 0x0115, kMaxVersion}, |
| {0x1050, 0x0116, kMaxVersion}, |
| {0x1050, 0x0120, kMaxVersion}, |
| {0x1050, 0x0200, kMaxVersion}, |
| {0x1050, 0x0211, kMaxVersion}, |
| {0x1050, 0x0401, kMaxVersion}, |
| {0x1050, 0x0402, kMaxVersion}, |
| {0x1050, 0x0403, kMaxVersion}, |
| {0x1050, 0x0404, kMaxVersion}, |
| {0x1050, 0x0405, kMaxVersion}, |
| {0x1050, 0x0406, kMaxVersion}, |
| {0x1050, 0x0407, kMaxVersion}, |
| {0x1050, 0x0410, kMaxVersion}, |
| |
| {0x10c4, 0x8acf, kMaxVersion}, // U2F Zero |
| {0x18d1, 0x5026, kMaxVersion}, // Titan |
| {0x1a44, 0x00bb, kMaxVersion}, // VASCO |
| {0x1e0d, 0xf1ae, kMaxVersion}, // Keydo AES |
| {0x1e0d, 0xf1d0, kMaxVersion}, // Neowave Keydo |
| {0x1ea8, 0xf025, kMaxVersion}, // Thetis |
| {0x20a0, 0x4287, kMaxVersion}, // Nitrokey |
| {0x24dc, 0x0101, kMaxVersion}, // JaCarta |
| {0x2581, 0xf1d0, kMaxVersion}, // Happlink |
| {0x2abe, 0x1002, kMaxVersion}, // Bluink |
| {0x2ccf, 0x0880, kMaxVersion}, // Feitian USB, HyperFIDO |
| }; |
| |
| } // namespace |
| |
| UsbBlocklist::Entry::Entry(uint16_t vendor_id, |
| uint16_t product_id, |
| uint16_t max_version) |
| : vendor_id(vendor_id), product_id(product_id), max_version(max_version) {} |
| |
| UsbBlocklist::~UsbBlocklist() {} |
| |
| // static |
| UsbBlocklist& UsbBlocklist::Get() { |
| return g_singleton.Get(); |
| } |
| |
| bool UsbBlocklist::IsExcluded(const Entry& entry) const { |
| return EntryMatches(std::begin(kStaticEntries), std::end(kStaticEntries), |
| entry) || |
| EntryMatches(dynamic_entries_.begin(), dynamic_entries_.end(), entry); |
| } |
| |
| bool UsbBlocklist::IsExcluded( |
| const device::mojom::UsbDeviceInfo& device_info) const { |
| uint16_t device_version = device_info.device_version_major << 8 | |
| device_info.device_version_minor << 4 | |
| device_info.device_version_subminor; |
| return IsExcluded( |
| Entry(device_info.vendor_id, device_info.product_id, device_version)); |
| } |
| |
| void UsbBlocklist::ResetToDefaultValuesForTest() { |
| dynamic_entries_.clear(); |
| PopulateWithServerProvidedValues(); |
| } |
| |
| UsbBlocklist::UsbBlocklist() { |
| DCHECK(std::is_sorted(std::begin(kStaticEntries), std::end(kStaticEntries), |
| CompareEntry)); |
| PopulateWithServerProvidedValues(); |
| } |
| |
| void UsbBlocklist::PopulateWithServerProvidedValues() { |
| std::string blocklist_string = variations::GetVariationParamValue( |
| "WebUSBBlocklist", "blocklist_additions"); |
| |
| for (const auto& entry : |
| base::SplitStringPiece(blocklist_string, ",", base::TRIM_WHITESPACE, |
| base::SPLIT_WANT_NONEMPTY)) { |
| std::vector<base::StringPiece> components = base::SplitStringPiece( |
| entry, ":", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL); |
| if (components.size() != 3 || !IsHexComponent(components[0]) || |
| !IsHexComponent(components[1]) || !IsHexComponent(components[2])) { |
| continue; |
| } |
| |
| uint32_t vendor_id; |
| uint32_t product_id; |
| uint32_t max_version; |
| if (!base::HexStringToUInt(components[0], &vendor_id) || |
| !base::HexStringToUInt(components[1], &product_id) || |
| !base::HexStringToUInt(components[2], &max_version)) { |
| continue; |
| } |
| |
| dynamic_entries_.emplace_back(vendor_id, product_id, max_version); |
| } |
| |
| std::sort(dynamic_entries_.begin(), dynamic_entries_.end(), CompareEntry); |
| } |