// Copyright 2019 Robin Krahl // SPDX-License-Identifier: GPL-3.0-or-later use serde::{Deserialize, Serialize}; use serde_big_array::big_array; use static_assertions::assert_eq_size; use usb_device::bus::{UsbBus, UsbBusAllocator}; use usb_device::device::{UsbDevice, UsbDeviceBuilder, UsbVidPid}; use crate::commands::CommandId; use crate::crc::Crc; use crate::hid::{HidDevice, Protocol, ReportType, Subclass}; use crate::util::TryFrom; const VID_CLAY_LOGIC: u16 = 0x20a0; const PID_NITROKEY_PRO: u16 = 0x4108; pub const REPORT_LEN: usize = 64; pub const REQUEST_DATA_LEN: usize = REPORT_LEN - 5; pub const RESPONSE_DATA_LEN: usize = REPORT_LEN - 11; 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, ]; pub type RequestData = [u8; REQUEST_DATA_LEN]; pub type ResponseData = [u8; RESPONSE_DATA_LEN]; big_array! { BigArray; RESPONSE_DATA_LEN, REQUEST_DATA_LEN, } 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, } } #[derive(Deserialize)] struct Request { pub command_id: u8, #[serde(with = "BigArray")] pub data: RequestData, pub crc: u32, } assert_eq_size!([u8; REPORT_LEN], Request); #[derive(Serialize)] struct Response { pub device_status: DeviceStatus, pub command_id: u8, pub last_crc: u32, pub command_status: CommandStatus, #[serde(with = "BigArray")] pub data: ResponseData, pub crc: u32, } assert_eq_size!([u8; REPORT_LEN], Response); impl Response { fn new(device_status: DeviceStatus, command_id: u8, last_crc: u32) -> Response { Response { device_status, command_id, last_crc, command_status: CommandStatus::NotSupported, data: [0; RESPONSE_DATA_LEN], crc: 0, } } } pub struct Nitrokey { crc: C, request: Option, request_crc: u32, buf: [u8; REPORT_LEN], } impl Nitrokey { pub fn new(crc: C) -> Self { Nitrokey { crc, request: None, request_crc: 0, buf: [0; REPORT_LEN], } } } 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(()); } if let Some(ref request) = self.request { let mut response = Response::new(DeviceStatus::Ok, request.command_id, request.crc); response.command_status = if self.request_crc == request.crc { if let Ok(command_id) = CommandId::try_from(request.command_id) { command_id.execute(&request.data, &mut response.data) } else { CommandStatus::UnknownCommand } } else { CommandStatus::WrongCrc }; // TODO: calculate actual CRC response.crc = 1; // libnitrokey accepts any non-zero value let len = ssmarshal::serialize(&mut self.buf, &response).map_err(|_| ())?; assert!(len == REPORT_LEN); Ok(&self.buf) } else { Err(()) } } 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 { let (request, len) = ssmarshal::deserialize(data).map_err(|_| ())?; assert!(len == REPORT_LEN); // the last four bytes are ignored as they contain the CRC self.request_crc = self.crc.get(&data[..REPORT_LEN - 4]); self.request = Some(request); 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() }