// Copyright 2019 Robin Krahl // SPDX-License-Identifier: GPL-3.0-or-later use usb_device::bus::{UsbBus, UsbBusAllocator}; use usb_device::device::{UsbDevice, UsbDeviceBuilder, UsbVidPid}; use crate::hid::{HidDevice, Protocol, ReportType, Subclass}; use crate::util::TryFrom; const VID_CLAY_LOGIC: u16 = 0x20a0; const PID_NITROKEY_PRO: u16 = 0x4108; const COMMAND_LEN: usize = 53; const REPORT_LEN: usize = 64; const REPORT_DESCRIPTOR: &[u8] = &[ 0x05, 0x01, 0x09, 0x06, 0xA1, 0x01, 0x05, 0x07, 0x19, 0xE0, 0x29, 0xE7, 0x15, 0x00, 0x25, 0x01, 0x75, 0x01, 0x95, 0x08, 0x81, 0x02, 0x95, 0x01, 0x75, 0x08, 0x81, 0x03, 0x95, 0x05, 0x75, 0x01, 0x05, 0x08, 0x19, 0x01, 0x29, 0x05, 0x91, 0x02, 0x95, 0x01, 0x75, 0x03, 0x91, 0x03, 0x95, 0x06, 0x75, 0x08, 0x15, 0x00, 0x25, 0x65, 0x05, 0x07, 0x19, 0x00, 0x29, 0x65, 0x81, 0x00, 0x09, 0x03, 0x75, 0x08, 0x95, 0x40, 0xB1, 0x02, 0xC0, ]; enum_u8! { #[derive(Clone, Copy, Debug, PartialEq)] pub enum DeviceStatus { Ok = 0, Busy = 1, Error = 2, ReceivedReport = 3, } } enum_u8! { #[derive(Clone, Copy, Debug, PartialEq)] pub enum CommandStatus { Ok = 0, WrongCrc = 1, WrongSlot = 2, SlotNotProgrammed = 3, WrongPassword = 4, NotAuthorized = 5, TimestampWarning = 6, NoNameError = 7, NotSupported = 8, UnknownCommand = 9, AesDecryptionFailed = 10, } } enum_u8! { #[derive(Clone, Copy, Debug, PartialEq)] pub enum CommandId { GetStatus = 0, ReadSlotName = 2, } } pub struct Nitrokey { buf: [u8; REPORT_LEN], } impl Nitrokey { pub fn new() -> Self { Nitrokey { buf: [0; REPORT_LEN], } } fn execute_command(&self, command_id: CommandId, data: &[u8], buf: &mut [u8]) -> CommandStatus { let data: &[u8] = match command_id { CommandId::GetStatus => { &[ 1, // firmware_version_st.minor 0, // firmware_version_st.major 0, // card_serial[0] 0, // card_serial[1] 0, // card_serial[2] 0, // card_serial[3] 0, // numlock 0, // capslock 0, // enable_user_password 0, // delete_user_password ] } CommandId::ReadSlotName => { assert!(data.len() > 1); let slot_number = data[0]; if slot_number != 0x20 { return CommandStatus::SlotNotProgrammed; } &[ 0x74, // t 0x65, // e 0x73, // s 0x74, // t 0x00, // NULL ] } }; assert!(buf.len() >= data.len()); buf[..data.len()].copy_from_slice(data); CommandStatus::Ok } } impl HidDevice for Nitrokey { fn subclass(&self) -> Subclass { Subclass::BootInterface } fn protocol(&self) -> Protocol { Protocol::Keyboard } fn report_descriptor(&self) -> &[u8] { REPORT_DESCRIPTOR } fn get_report(&mut self, report_type: ReportType, report_id: u8) -> Result<&[u8], ()> { if report_type != ReportType::Feature || report_id != 0 { return Err(()); } const PREFIX_LEN: usize = 7; const SUFFIX_LEN: usize = 4; const PREFIX_END: usize = PREFIX_LEN; const SUFFIX_START: usize = REPORT_LEN - SUFFIX_LEN; let mut buf = [0; COMMAND_LEN]; let device_status = DeviceStatus::Ok; let command_id = self.buf[0]; let command_status = if let Ok(command_id) = CommandId::try_from(command_id) { self.execute_command(command_id, &self.buf[1..60], &mut buf) } else { CommandStatus::UnknownCommand }; let pre_data: [u8; PREFIX_LEN] = [ device_status.into(), // device status command_id, // command_id self.buf[60], self.buf[61], self.buf[62], self.buf[63], // last_command_crc command_status.into(), // last_command_status ]; let post_data: [u8; SUFFIX_LEN] = [ 0x00, 0x00, 0x00, 0x01, // crc -- libnitrokey accepts any non-zero value ]; self.buf[..PREFIX_END].copy_from_slice(&pre_data); self.buf[PREFIX_END..SUFFIX_START].copy_from_slice(&buf); self.buf[SUFFIX_START..].copy_from_slice(&post_data); Ok(&self.buf) } fn set_report( &mut self, report_type: ReportType, report_id: u8, data: &[u8], ) -> Result<(), ()> { if report_type != ReportType::Feature || report_id != 0 || data.len() != REPORT_LEN { Err(()) } else { self.buf.copy_from_slice(data); Ok(()) } } } pub fn create_usb_device(alloc: &UsbBusAllocator) -> UsbDevice<'_, B> { UsbDeviceBuilder::new(alloc, UsbVidPid(VID_CLAY_LOGIC, PID_NITROKEY_PRO)) .manufacturer("Nitrokey/ntw") .product("Nitrokey Pro/ntw") .serial_number("?") .build() }