1#!/usr/bin/env python 2# Copyright 2016 Google Inc. All Rights Reserved. 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); 5# you may not use this file except in compliance with the License. 6# You may obtain a copy of the License at 7# 8# http://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, 12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13# See the License for the specific language governing permissions and 14# limitations under the License. 15 16"""Implements raw HID interface on Linux using SysFS and device files.""" 17from __future__ import division 18 19import os 20import struct 21 22from pyu2f import errors 23from pyu2f.hid import base 24 25REPORT_DESCRIPTOR_KEY_MASK = 0xfc 26LONG_ITEM_ENCODING = 0xfe 27OUTPUT_ITEM = 0x90 28INPUT_ITEM = 0x80 29COLLECTION_ITEM = 0xa0 30REPORT_COUNT = 0x94 31REPORT_SIZE = 0x74 32USAGE_PAGE = 0x04 33USAGE = 0x08 34 35 36def GetValueLength(rd, pos): 37 """Get value length for a key in rd. 38 39 For a key at position pos in the Report Descriptor rd, return the length 40 of the associated value. This supports both short and long format 41 values. 42 43 Args: 44 rd: Report Descriptor 45 pos: The position of the key in rd. 46 47 Returns: 48 (key_size, data_len) where key_size is the number of bytes occupied by 49 the key and data_len is the length of the value associated by the key. 50 """ 51 rd = bytearray(rd) 52 key = rd[pos] 53 if key == LONG_ITEM_ENCODING: 54 # If the key is tagged as a long item (0xfe), then the format is 55 # [key (1 byte)] [data len (1 byte)] [item tag (1 byte)] [data (n # bytes)]. 56 # Thus, the entire key record is 3 bytes long. 57 if pos + 1 < len(rd): 58 return (3, rd[pos + 1]) 59 else: 60 raise errors.HidError('Malformed report descriptor') 61 62 else: 63 # If the key is tagged as a short item, then the item tag and data len are 64 # packed into one byte. The format is thus: 65 # [tag (high 4 bits)] [type (2 bits)] [size code (2 bits)] [data (n bytes)]. 66 # The size code specifies 1,2, or 4 bytes (0x03 means 4 bytes). 67 code = key & 0x03 68 if code <= 0x02: 69 return (1, code) 70 elif code == 0x03: 71 return (1, 4) 72 73 raise errors.HidError('Cannot happen') 74 75 76def ReadLsbBytes(rd, offset, value_size): 77 """Reads value_size bytes from rd at offset, least signifcant byte first.""" 78 79 encoding = None 80 if value_size == 1: 81 encoding = '<B' 82 elif value_size == 2: 83 encoding = '<H' 84 elif value_size == 4: 85 encoding = '<L' 86 else: 87 raise errors.HidError('Invalid value size specified') 88 89 ret, = struct.unpack(encoding, rd[offset:offset + value_size]) 90 return ret 91 92 93class NoReportCountFound(Exception): 94 pass 95 96 97def ParseReportDescriptor(rd, desc): 98 """Parse the binary report descriptor. 99 100 Parse the binary report descriptor into a DeviceDescriptor object. 101 102 Args: 103 rd: The binary report descriptor 104 desc: The DeviceDescriptor object to update with the results 105 from parsing the descriptor. 106 107 Returns: 108 None 109 """ 110 rd = bytearray(rd) 111 112 pos = 0 113 report_count = None 114 report_size = None 115 usage_page = None 116 usage = None 117 118 while pos < len(rd): 119 key = rd[pos] 120 121 # First step, determine the value encoding (either long or short). 122 key_size, value_length = GetValueLength(rd, pos) 123 124 if key & REPORT_DESCRIPTOR_KEY_MASK == INPUT_ITEM: 125 if report_count and report_size: 126 byte_length = (report_count * report_size) // 8 127 desc.internal_max_in_report_len = max( 128 desc.internal_max_in_report_len, byte_length) 129 report_count = None 130 report_size = None 131 elif key & REPORT_DESCRIPTOR_KEY_MASK == OUTPUT_ITEM: 132 if report_count and report_size: 133 byte_length = (report_count * report_size) // 8 134 desc.internal_max_out_report_len = max( 135 desc.internal_max_out_report_len, byte_length) 136 report_count = None 137 report_size = None 138 elif key & REPORT_DESCRIPTOR_KEY_MASK == COLLECTION_ITEM: 139 if usage_page: 140 desc.usage_page = usage_page 141 if usage: 142 desc.usage = usage 143 elif key & REPORT_DESCRIPTOR_KEY_MASK == REPORT_COUNT: 144 if len(rd) >= pos + 1 + value_length: 145 report_count = ReadLsbBytes(rd, pos + 1, value_length) 146 elif key & REPORT_DESCRIPTOR_KEY_MASK == REPORT_SIZE: 147 if len(rd) >= pos + 1 + value_length: 148 report_size = ReadLsbBytes(rd, pos + 1, value_length) 149 elif key & REPORT_DESCRIPTOR_KEY_MASK == USAGE_PAGE: 150 if len(rd) >= pos + 1 + value_length: 151 usage_page = ReadLsbBytes(rd, pos + 1, value_length) 152 elif key & REPORT_DESCRIPTOR_KEY_MASK == USAGE: 153 if len(rd) >= pos + 1 + value_length: 154 usage = ReadLsbBytes(rd, pos + 1, value_length) 155 156 pos += value_length + key_size 157 return desc 158 159 160def ParseUevent(uevent, desc): 161 lines = uevent.split(b'\n') 162 for line in lines: 163 line = line.strip() 164 if not line: 165 continue 166 k, v = line.split(b'=') 167 if k == b'HID_NAME': 168 desc.product_string = v.decode('utf8') 169 elif k == b'HID_ID': 170 _, vid, pid = v.split(b':') 171 desc.vendor_id = int(vid, 16) 172 desc.product_id = int(pid, 16) 173 174 175class LinuxHidDevice(base.HidDevice): 176 """Implementation of HID device for linux. 177 178 Implementation of HID device interface for linux that uses block 179 devices to interact with the device and sysfs to enumerate/discover 180 device metadata. 181 """ 182 183 @staticmethod 184 def Enumerate(): 185 for hidraw in os.listdir('/sys/class/hidraw'): 186 rd_path = ( 187 os.path.join( 188 '/sys/class/hidraw', hidraw, 189 'device/report_descriptor')) 190 uevent_path = os.path.join('/sys/class/hidraw', hidraw, 'device/uevent') 191 rd_file = open(rd_path, 'rb') 192 uevent_file = open(uevent_path, 'rb') 193 desc = base.DeviceDescriptor() 194 desc.path = os.path.join('/dev/', hidraw) 195 ParseReportDescriptor(rd_file.read(), desc) 196 ParseUevent(uevent_file.read(), desc) 197 198 rd_file.close() 199 uevent_file.close() 200 yield desc.ToPublicDict() 201 202 def __init__(self, path): 203 base.HidDevice.__init__(self, path) 204 self.dev = os.open(path, os.O_RDWR) 205 self.desc = base.DeviceDescriptor() 206 self.desc.path = path 207 rd_file = open(os.path.join('/sys/class/hidraw', 208 os.path.basename(path), 209 'device/report_descriptor'), 'rb') 210 ParseReportDescriptor(rd_file.read(), self.desc) 211 rd_file.close() 212 213 def GetInReportDataLength(self): 214 """See base class.""" 215 return self.desc.internal_max_in_report_len 216 217 def GetOutReportDataLength(self): 218 """See base class.""" 219 return self.desc.internal_max_out_report_len 220 221 def Write(self, packet): 222 """See base class.""" 223 out = bytearray([0] + packet) # Prepend the zero-byte (report ID) 224 os.write(self.dev, out) 225 226 def Read(self): 227 """See base class.""" 228 raw_in = os.read(self.dev, self.GetInReportDataLength()) 229 decoded_in = list(bytearray(raw_in)) 230 return decoded_in 231