/*
 * Was originally u2f_hidraw_id.c,
 * Copyright (c) 2014-2015 Andrew Lutomirski
 * 
 * Mangled into isu2f.c by hackerb9 in 2022 as a demonstration for firejail.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <stdio.h>		/* fprintf() */
#include <string.h>		/* strlen() */
#include <fcntl.h>		/* open() */
#include <unistd.h>		/* read(), close() */
#include <linux/hid.h>		/* HID_MAX_DESCRIPTOR_SIZE */

#define HID_RPTDESC_FIRST_BYTE_LONG_ITEM 0xfe
#define HID_RPTDESC_TYPE_GLOBAL 0x1
#define HID_RPTDESC_TYPE_LOCAL 0x2
#define HID_RPTDESC_GLOBAL_ITEM_USAGE_PAGE 0x0
#define HID_RPTDESC_LOCAL_ITEM_USAGE 0x0

int main(int argc, char **argv)
{
        char *path = NULL;
        unsigned char desc[HID_MAX_DESCRIPTOR_SIZE];
        int desclen;
        int fd = -1;
        int i;
        int ret = 1;
        unsigned int usage_page = 0;
        int is_u2f_token = 0;

        if (argc < 2) {
                fprintf(stderr, "Usage: isu2f /sys/class/hidraw/*/device/report_descriptor\n");
                return 1;
        }

	while (*(++argv)) {
		path = *argv;
		is_u2f_token = 0;

		fd = open(path, O_RDONLY | O_NOFOLLOW);
		if (fd == -1) {
			perror(path);
			continue;
		}

		desclen = read(fd, desc, sizeof(desc));
		if (desclen < 0) {
			perror(path);
			continue;
			/* XXX Maybe should handle EINTR or short reads */
		}
		if (desclen == 0) {
			fprintf(stderr, "%s: Empty\n", path);
			continue;
		}

		/* Parse the report descriptor. */
		for (i = 0; i < desclen; ) {
			/*
			 * The first byte of the report descriptor is a tag, a type,
			 * and a code that helps determine the size.
			 */
			unsigned char tag = desc[i] >> 4;
			unsigned char type = (desc[i] >> 2) & 0x3;
			unsigned char sizecode = desc[i] & 0x3;

			int size, j;
			unsigned int value = 0;

			if (desc[i] == HID_RPTDESC_FIRST_BYTE_LONG_ITEM) {
				/* Long item; skip it. */
				if (i + 1 >= desclen) {
					fprintf(stderr, "%s: Bad report descriptor: EOF when expecting long_item length.\n", path);
					break;
				}
				i += (desc[i+1] + 3);  /* Can't overflow. */
				continue;
			}

			size = (sizecode < 3 ? sizecode : 4);
			if (i + 1 + size > desclen) {
				fprintf(stderr, "%s: Bad report descriptor: EOF when expecting item length.\n", path);
				i += 1 + size;
				continue;
			}

			for (j = 0; j < size; j++)
				value |= (desc[i + 1 + j] << 8*j);

			if (type == HID_RPTDESC_TYPE_GLOBAL &&
			    tag == HID_RPTDESC_GLOBAL_ITEM_USAGE_PAGE)
				usage_page = value;

			/*
			 * Detect U2F tokens.  See:
			 * https://fidoalliance.org/specs/fido-u2f-HID-protocol-v1.0-rd-20141008.pdf
			 * http://www.usb.org/developers/hidpage/HUTRR48.pdf
			 */

			if (type == HID_RPTDESC_TYPE_LOCAL &&
			    tag == HID_RPTDESC_LOCAL_ITEM_USAGE) {
				if (usage_page == 0xf1d0 && value == 0x1) {
					is_u2f_token = 1;
					break;
				}
			}

			i += 1 + size;
		}


		/* Fit in 80 cols: lop off "/report_descriptor" */
		int l=strlen(path);
		if (l>18 && (strcmp(path+l-18, "/report_descriptor") == 0))
			path[l-18] = '\0';

		printf("%s: %s\n", path, 
		       (is_u2f_token)?
		       "Yup, this is a U2F_TOKEN":
		       "Nope, not a U2F_TOKEN");

		ret = 0;

		if (fd != -1)
			close(fd);

	} /* Matches  'while (++argv) {...'  */


        if (fd != -1)
                close(fd);

        return ret;
}