// Copyright 2015 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 #include #include #include #include "base/barrier_closure.h" #include "base/bind.h" #include "base/callback.h" #include "base/logging.h" #include "components/device_event_log/device_event_log.h" #include "device/usb/usb_device_handle.h" #include "device/usb/webusb_descriptors.h" #include "net/base/io_buffer.h" using net::IOBufferWithSize; namespace device { namespace { // These constants are defined by the Universal Serial Device 3.0 Specification // Revision 1.0. const uint8_t kGetDescriptorRequest = 0x06; const uint8_t kBosDescriptorType = 0x0F; const uint8_t kDeviceCapabilityDescriptorType = 0x10; const uint8_t kPlatformDevCapabilityType = 0x05; // These constants are defined by the WebUSB specification: // http://wicg.github.io/webusb/ const uint8_t kGetAllowedOriginsRequest = 0x01; const uint8_t kGetUrlRequest = 0x02; const uint8_t kWebUsbCapabilityUUID[16] = { // Little-endian encoding of {3408b638-09a9-47a0-8bfd-a0768815b665}. 0x38, 0xB6, 0x08, 0x34, 0xA9, 0x09, 0xA0, 0x47, 0x8B, 0xFD, 0xA0, 0x76, 0x88, 0x15, 0xB6, 0x65}; const int kControlTransferTimeout = 60000; // 1 minute using ReadWebUsbDescriptorsCallback = base::Callback allowed_origins, const GURL& landing_page)>; using ReadWebUsbAllowedOriginsCallback = base::Callback allowed_origins)>; // Parses a WebUSB Function Subset Header: // http://wicg.github.io/webusb/#dfn-function-subset-header // // 0 1 2 3 // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | length | type | 1st interface | origin[0] | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | origin[1] | ... // +-+-+-+-+-+-+-+-+-+-+-+------ bool ParseFunction(WebUsbFunctionSubset* function, std::vector::const_iterator* it, std::vector::const_iterator end) { const uint8_t kDescriptorType = 0x02; const uint8_t kDescriptorMinLength = 3; // If this isn't the end of the buffer then there must be at least one byte // available, the length of the descriptor. if (*it == end) return false; uint8_t length = (*it)[0]; // Is this a valid Function Subset Header? It must be long enough, fit within // the buffer and have the right descriptor type. if (length < kDescriptorMinLength || std::distance(*it, end) < length || (*it)[1] != kDescriptorType) { return false; } function->first_interface = (*it)[2]; // Everything after the mandatory fields are origin indicies. std::advance(*it, kDescriptorMinLength); uint8_t num_origins = length - kDescriptorMinLength; function->origin_ids.reserve(num_origins); for (size_t i = 0; i < num_origins; ++i) { uint8_t index = *(*it)++; if (index == 0) return false; function->origin_ids.push_back(index); } return true; } // Parses a WebUSB Configuration Subset Header: // http://wicg.github.io/webusb/#dfn-configuration-subset-header // // 0 1 2 3 // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | length | type | config value | num functions | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | origin[0] | origin[1] | ... // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+------ bool ParseConfiguration(WebUsbConfigurationSubset* configuration, std::vector::const_iterator* it, std::vector::const_iterator end) { const uint8_t kDescriptorType = 0x01; const uint8_t kDescriptorMinLength = 4; // If this isn't the end of the buffer then there must be at least one byte // available, the length of the descriptor. if (*it == end) return false; uint8_t length = (*it)[0]; // Is this a valid Configuration Subset Header? It must be long enough, fit // within the buffer and have the right descriptor type. if (length < kDescriptorMinLength || std::distance(*it, end) < length || (*it)[1] != kDescriptorType) { return false; } configuration->configuration_value = (*it)[2]; uint8_t num_functions = (*it)[3]; // The next |length - 4| bytes after the mandatory fields are origin indicies. std::advance(*it, kDescriptorMinLength); uint8_t num_origins = length - kDescriptorMinLength; configuration->origin_ids.reserve(num_origins); for (size_t i = 0; i < num_origins; ++i) { uint8_t index = *(*it)++; if (index == 0) return false; configuration->origin_ids.push_back(index); } // |num_functions| function descriptors then follow the descriptor. for (size_t i = 0; i < num_functions; ++i) { WebUsbFunctionSubset function; if (!ParseFunction(&function, it, end)) return false; configuration->functions.push_back(function); } return true; } void OnDoneReadingUrls(scoped_ptr allowed_origins, uint8_t landing_page_id, scoped_ptr> url_map, const ReadWebUsbDescriptorsCallback& callback) { for (uint8_t origin_id : allowed_origins->origin_ids) { const auto& it = url_map->find(origin_id); if (it != url_map->end()) allowed_origins->origins.push_back(it->second.GetOrigin()); } for (auto& configuration : allowed_origins->configurations) { for (uint8_t origin_id : configuration.origin_ids) { const auto& it = url_map->find(origin_id); if (it != url_map->end()) configuration.origins.push_back(it->second.GetOrigin()); } for (auto& function : configuration.functions) { for (uint8_t origin_id : function.origin_ids) { const auto& it = url_map->find(origin_id); if (it != url_map->end()) function.origins.push_back(it->second.GetOrigin()); } } } GURL landing_page; if (landing_page_id != 0) landing_page = (*url_map)[landing_page_id]; callback.Run(std::move(allowed_origins), landing_page); } void OnReadUrlDescriptor(std::map* url_map, uint8_t index, const base::Closure& callback, UsbTransferStatus status, scoped_refptr buffer, size_t length) { if (status != USB_TRANSFER_COMPLETED) { USB_LOG(EVENT) << "Failed to read WebUSB URL descriptor: " << index; callback.Run(); return; } GURL url; if (ParseWebUsbUrlDescriptor( std::vector(buffer->data(), buffer->data() + length), &url)) { (*url_map)[index] = url; } callback.Run(); } // Reads the descriptor with |index| from the device, adds the value to // |url_map| and then runs |callback|. void ReadUrlDescriptor(scoped_refptr device_handle, uint8_t vendor_code, std::map* url_map, uint8_t index, const base::Closure& callback) { scoped_refptr buffer = new IOBufferWithSize(255); device_handle->ControlTransfer( USB_DIRECTION_INBOUND, UsbDeviceHandle::VENDOR, UsbDeviceHandle::DEVICE, vendor_code, index, kGetUrlRequest, buffer, buffer->size(), kControlTransferTimeout, base::Bind(&OnReadUrlDescriptor, url_map, index, callback)); } // Reads URL descriptors from the device so that it can fill |allowed_origins| // with the GURLs matching the indicies already collected. void ReadUrlDescriptors(scoped_refptr device_handle, uint8_t vendor_code, uint8_t landing_page_id, const ReadWebUsbDescriptorsCallback& callback, scoped_ptr allowed_origins) { if (!allowed_origins) { callback.Run(nullptr, GURL()); return; } std::set to_request; if (landing_page_id != 0) to_request.insert(landing_page_id); to_request.insert(allowed_origins->origin_ids.begin(), allowed_origins->origin_ids.end()); for (auto& config : allowed_origins->configurations) { to_request.insert(config.origin_ids.begin(), config.origin_ids.end()); for (auto& function : config.functions) { to_request.insert(function.origin_ids.begin(), function.origin_ids.end()); } } scoped_ptr> url_map(new std::map()); std::map* url_map_ptr = url_map.get(); base::Closure barrier = base::BarrierClosure( static_cast(to_request.size()), base::Bind(&OnDoneReadingUrls, base::Passed(&allowed_origins), landing_page_id, base::Passed(&url_map), callback)); for (uint8_t index : to_request) { ReadUrlDescriptor(device_handle, vendor_code, url_map_ptr, index, barrier); } } void OnReadWebUsbAllowedOrigins( const ReadWebUsbAllowedOriginsCallback& callback, UsbTransferStatus status, scoped_refptr buffer, size_t length) { if (status != USB_TRANSFER_COMPLETED) { USB_LOG(EVENT) << "Failed to read WebUSB allowed origins."; callback.Run(nullptr); return; } scoped_ptr allowed_origins(new WebUsbAllowedOrigins()); if (allowed_origins->Parse( std::vector(buffer->data(), buffer->data() + length))) { callback.Run(std::move(allowed_origins)); } else { callback.Run(nullptr); } } void OnReadWebUsbAllowedOriginsHeader( scoped_refptr device_handle, const ReadWebUsbAllowedOriginsCallback& callback, uint8_t vendor_code, UsbTransferStatus status, scoped_refptr buffer, size_t length) { if (status != USB_TRANSFER_COMPLETED || length != 4) { USB_LOG(EVENT) << "Failed to read WebUSB allowed origins header."; callback.Run(nullptr); return; } uint16_t new_length = buffer->data()[2] | (buffer->data()[3] << 8); scoped_refptr new_buffer = new IOBufferWithSize(new_length); device_handle->ControlTransfer( USB_DIRECTION_INBOUND, UsbDeviceHandle::VENDOR, UsbDeviceHandle::DEVICE, vendor_code, 0, kGetAllowedOriginsRequest, new_buffer, new_buffer->size(), kControlTransferTimeout, base::Bind(&OnReadWebUsbAllowedOrigins, callback)); } void ReadWebUsbAllowedOrigins( scoped_refptr device_handle, uint8_t vendor_code, const ReadWebUsbAllowedOriginsCallback& callback) { scoped_refptr buffer = new IOBufferWithSize(4); device_handle->ControlTransfer( USB_DIRECTION_INBOUND, UsbDeviceHandle::VENDOR, UsbDeviceHandle::DEVICE, vendor_code, 0, kGetAllowedOriginsRequest, buffer, buffer->size(), kControlTransferTimeout, base::Bind(&OnReadWebUsbAllowedOriginsHeader, device_handle, callback, vendor_code)); } void OnReadBosDescriptor(scoped_refptr device_handle, const ReadWebUsbDescriptorsCallback& callback, UsbTransferStatus status, scoped_refptr buffer, size_t length) { if (status != USB_TRANSFER_COMPLETED) { USB_LOG(EVENT) << "Failed to read BOS descriptor."; callback.Run(nullptr, GURL()); return; } WebUsbPlatformCapabilityDescriptor descriptor; if (!descriptor.ParseFromBosDescriptor( std::vector(buffer->data(), buffer->data() + length))) { callback.Run(nullptr, GURL()); return; } ReadWebUsbAllowedOrigins( device_handle, descriptor.vendor_code, base::Bind(&ReadUrlDescriptors, device_handle, descriptor.vendor_code, descriptor.landing_page_id, callback)); } void OnReadBosDescriptorHeader(scoped_refptr device_handle, const ReadWebUsbDescriptorsCallback& callback, UsbTransferStatus status, scoped_refptr buffer, size_t length) { if (status != USB_TRANSFER_COMPLETED || length != 5) { USB_LOG(EVENT) << "Failed to read BOS descriptor header."; callback.Run(nullptr, GURL()); return; } uint16_t new_length = buffer->data()[2] | (buffer->data()[3] << 8); scoped_refptr new_buffer = new IOBufferWithSize(new_length); device_handle->ControlTransfer( USB_DIRECTION_INBOUND, UsbDeviceHandle::STANDARD, UsbDeviceHandle::DEVICE, kGetDescriptorRequest, kBosDescriptorType << 8, 0, new_buffer, new_buffer->size(), kControlTransferTimeout, base::Bind(&OnReadBosDescriptor, device_handle, callback)); } } // namespace WebUsbFunctionSubset::WebUsbFunctionSubset() : first_interface(0) {} WebUsbFunctionSubset::WebUsbFunctionSubset(const WebUsbFunctionSubset& other) = default; WebUsbFunctionSubset::~WebUsbFunctionSubset() {} WebUsbConfigurationSubset::WebUsbConfigurationSubset() : configuration_value(0) {} WebUsbConfigurationSubset::WebUsbConfigurationSubset( const WebUsbConfigurationSubset& other) = default; WebUsbConfigurationSubset::~WebUsbConfigurationSubset() {} WebUsbAllowedOrigins::WebUsbAllowedOrigins() {} WebUsbAllowedOrigins::~WebUsbAllowedOrigins() {} // Parses a WebUSB Allowed Origins Header: // http://wicg.github.io/webusb/#dfn-allowed-origins-header // // 0 1 2 3 // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | length | type | total length | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | num configs | origin[0] | origin[1] | ... // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+------ bool WebUsbAllowedOrigins::Parse(const std::vector& bytes) { const uint8_t kDescriptorType = 0x00; const uint8_t kDescriptorMinLength = 5; // The buffer must be at least the length of this descriptor's mandatory // fields. if (bytes.size() < kDescriptorMinLength) return false; // Validate that the length of this descriptor and the total length of the // entire block of descriptors is consistent with the length of the buffer. uint8_t length = bytes[0]; uint16_t total_length = bytes[2] + (bytes[3] << 8); if (length < 5 || length > bytes.size() || // bLength bytes[1] != kDescriptorType || // bDescriptorType total_length < length || total_length > bytes.size()) { // wTotalLength return false; } std::vector::const_iterator it = bytes.begin(); uint8_t num_configurations = bytes[4]; // The next |length - 5| bytes after the mandatory fields are origin indicies. std::advance(it, kDescriptorMinLength); uint8_t num_origins = length - kDescriptorMinLength; origin_ids.reserve(num_origins); for (size_t i = 0; i < num_origins; ++i) { uint8_t index = *it++; if (index == 0) return false; origin_ids.push_back(index); } // |num_configurations| configuration descriptors then follow the descriptor. for (size_t i = 0; i < num_configurations; ++i) { WebUsbConfigurationSubset configuration; if (!ParseConfiguration(&configuration, &it, bytes.end())) return false; configurations.push_back(configuration); } return true; } WebUsbPlatformCapabilityDescriptor::WebUsbPlatformCapabilityDescriptor() : version(0), vendor_code(0) {} WebUsbPlatformCapabilityDescriptor::~WebUsbPlatformCapabilityDescriptor() {} bool WebUsbPlatformCapabilityDescriptor::ParseFromBosDescriptor( const std::vector& bytes) { if (bytes.size() < 5) { // Too short for the BOS descriptor header. return false; } // Validate the BOS descriptor, defined in Table 9-12 of the Universal Serial // Bus 3.1 Specification, Revision 1.0. uint16_t total_length = bytes[2] + (bytes[3] << 8); if (bytes[0] != 5 || // bLength bytes[1] != kBosDescriptorType || // bDescriptorType 5 > total_length || total_length > bytes.size()) { // wTotalLength return false; } uint8_t num_device_caps = bytes[4]; std::vector::const_iterator it = bytes.begin(); std::vector::const_iterator end = it + total_length; std::advance(it, 5); uint8_t length = 0; for (size_t i = 0; i < num_device_caps; ++i, std::advance(it, length)) { if (it == end) { return false; } // Validate the Device Capability descriptor, defined in Table 9-13 of the // Universal Serial Bus 3.1 Specification, Revision 1.0. length = it[0]; if (length < 3 || std::distance(it, end) < length || // bLength it[1] != kDeviceCapabilityDescriptorType) { // bDescriptorType return false; } if (it[2] != kPlatformDevCapabilityType) { // bDevCapabilityType continue; } // Validate the Platform Capability Descriptor, defined in Table 9-18 of the // Universal Serial Bus 3.1 Specification, Revision 1.0. if (length < 20) { // Platform capability descriptors must be at least 20 bytes. return false; } if (memcmp(&it[4], kWebUsbCapabilityUUID, sizeof(kWebUsbCapabilityUUID)) != 0) { // PlatformCapabilityUUID continue; } if (length < 22) { // The WebUSB capability descriptor must be at least 22 bytes (to allow // for future versions). return false; } version = it[20] + (it[21] << 8); // bcdVersion if (version < 0x0100) { continue; } // Version 1.0 defines two fields for a total length of 24 bytes. if (length != 24) { return false; } vendor_code = it[22]; landing_page_id = it[23]; return true; } return false; } // Parses a WebUSB URL Descriptor: // http://wicg.github.io/webusb/#dfn-url-descriptor // // 0 1 2 3 // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | length | type | prefix | data[0] | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | data[1] | ... // +-+-+-+-+-+-+-+-+-+-+-+------ bool ParseWebUsbUrlDescriptor(const std::vector& bytes, GURL* output) { const uint8_t kDescriptorType = 0x03; const uint8_t kDescriptorMinLength = 3; if (bytes.size() < kDescriptorMinLength) { return false; } // Validate that the length is consistent and fits within the buffer. uint8_t length = bytes[0]; if (length < kDescriptorMinLength || length < bytes.size() || bytes[1] != kDescriptorType) { return false; } // Look up the URL prefix and append the rest of the data in the descriptor. std::string url; switch (bytes[2]) { case 0: url.append("http://"); break; case 1: url.append("https://"); break; default: return false; } url.append(reinterpret_cast(bytes.data() + 3), length - 3); *output = GURL(url); if (!output->is_valid()) { return false; } return true; } void ReadWebUsbDescriptors(scoped_refptr device_handle, const ReadWebUsbDescriptorsCallback& callback) { scoped_refptr buffer = new IOBufferWithSize(5); device_handle->ControlTransfer( USB_DIRECTION_INBOUND, UsbDeviceHandle::STANDARD, UsbDeviceHandle::DEVICE, kGetDescriptorRequest, kBosDescriptorType << 8, 0, buffer, buffer->size(), kControlTransferTimeout, base::Bind(&OnReadBosDescriptorHeader, device_handle, callback)); } } // namespace device