/* * Copyright (c) 2015-2018 Nitrokey UG * * This file is part of libnitrokey. * * libnitrokey 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 3 of the License, or * any later version. * * libnitrokey 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 General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with libnitrokey. If not, see . * * SPDX-License-Identifier: LGPL-3.0 */ #ifndef DEVICE_PROTO_H #define DEVICE_PROTO_H #include #include #include #include #include // a local version for compatibility with Windows #include #include "cxx_semantics.h" #include "device.h" #include "misc.h" #include "log.h" #include "command_id.h" #include "dissect.h" #include "CommandFailedException.h" #include "LongOperationInProgressException.h" #define STICK20_UPDATE_MODE_VID 0x03EB #define STICK20_UPDATE_MODE_PID 0x2FF1 #define PAYLOAD_SIZE 53 #define PWS_SLOT_COUNT 16 #define PWS_SLOTNAME_LENGTH 11 #define PWS_PASSWORD_LENGTH 20 #define PWS_LOGINNAME_LENGTH 32 #define PWS_SEND_PASSWORD 0 #define PWS_SEND_LOGINNAME 1 #define PWS_SEND_TAB 2 #define PWS_SEND_CR 3 #include #include "DeviceCommunicationExceptions.h" #define bzero(b,len) (memset((b), '\0', (len)), (void) 0) namespace nitrokey { namespace proto { extern std::mutex send_receive_mtx; /* * POD types for HID proto commands * Instances are meant to be __packed. * * TODO (future) support for Big Endian */ #pragma pack (push,1) /* * Every packet is a USB HID report (check USB spec) */ template struct HIDReport { uint8_t _zero; CommandID command_id; // uint8_t union { uint8_t _padding[HID_REPORT_SIZE - 6]; Payload payload; } __packed; uint32_t crc; // POD types can't have non-default constructors // used in Transaction<>::run() void initialize() { bzero(this, sizeof *this); command_id = cmd_id; } uint32_t calculate_CRC() const { // w/o leading zero, a part of each HID packet // w/o 4-byte crc return misc::stm_crc32((const uint8_t *) (this) + 1, (size_t) (HID_REPORT_SIZE - 5)); } void update_CRC() { crc = calculate_CRC(); } bool isCRCcorrect() const { return crc == calculate_CRC(); } bool isValid() const { return true; // return !_zero && payload.isValid() && isCRCcorrect(); } operator std::string() const { // Packet type is known upfront in normal operation. // Can't be used to dissect random packets. return QueryDissector::dissect(*this); } } __packed; /* * Response payload (the parametrized type inside struct HIDReport) * * command_id member in incoming HIDReport structure carries the command * type last used. */ namespace DeviceResponseConstants{ //magic numbers from firmware static constexpr auto storage_status_absolute_address = 21; static constexpr auto storage_data_absolute_address = storage_status_absolute_address + 5; static constexpr auto header_size = 8; //from _zero to last_command_status inclusive static constexpr auto footer_size = 4; //crc static constexpr auto wrapping_size = header_size + footer_size; } template struct DeviceResponse { static constexpr auto storage_status_padding_size = DeviceResponseConstants::storage_status_absolute_address - DeviceResponseConstants::header_size; uint8_t _zero; uint8_t device_status; uint8_t command_id; // originally last_command_type uint32_t last_command_crc; uint8_t last_command_status; union { uint8_t _padding[HID_REPORT_SIZE - DeviceResponseConstants::wrapping_size]; ResponsePayload payload; struct { uint8_t _storage_status_padding[storage_status_padding_size]; uint8_t command_counter; uint8_t command_id; uint8_t device_status; //@see stick20::device_status uint8_t progress_bar_value; } __packed storage_status; } __packed; uint32_t crc; void initialize() { bzero(this, sizeof *this); } uint32_t calculate_CRC() const { // w/o leading zero, a part of each HID packet // w/o 4-byte crc return misc::stm_crc32((const uint8_t *) (this) + 1, (size_t) (HID_REPORT_SIZE - 5)); } void update_CRC() { crc = calculate_CRC(); } bool isCRCcorrect() const { return crc == calculate_CRC(); } bool isValid() const { // return !_zero && payload.isValid() && isCRCcorrect() && // command_id == (uint8_t)(cmd_id); return crc != 0; } operator std::string() const { return ResponseDissector::dissect(*this); } } __packed; struct EmptyPayload { bool isValid() const { return true; } std::string dissect() const { return std::string("Empty Payload."); } } __packed; template class ClearingProxy { public: ClearingProxy(command_packet &p) { packet = p; bzero(&p, sizeof(p)); } ~ClearingProxy() { bzero(&packet, sizeof(packet)); } response_payload &data() { return packet.payload; } command_packet packet; }; template class Transaction : semantics::non_constructible { public: // Types declared in command class scope can't be reached from there. typedef command_payload CommandPayload; typedef response_payload ResponsePayload; typedef struct HIDReport OutgoingPacket; typedef struct DeviceResponse ResponsePacket; #pragma pack (pop) static_assert(std::is_pod::value, "outgoingpacket must be a pod type"); static_assert(std::is_pod::value, "ResponsePacket must be a POD type"); static_assert(sizeof(OutgoingPacket) == HID_REPORT_SIZE, "OutgoingPacket type is not the right size"); static_assert(sizeof(ResponsePacket) == HID_REPORT_SIZE, "ResponsePacket type is not the right size"); static uint32_t getCRC( const command_payload &payload) { OutgoingPacket outp; outp.initialize(); outp.payload = payload; outp.update_CRC(); return outp.crc; } template static void clear_packet(T &st) { bzero(&st, sizeof(st)); } static ClearingProxy run(std::shared_ptr dev, const command_payload &payload) { using namespace ::nitrokey::device; using namespace ::nitrokey::log; using namespace std::chrono_literals; std::lock_guard guard(send_receive_mtx); LOG(__FUNCTION__, Loglevel::DEBUG_L2); if (dev == nullptr){ LOG(std::string("Throw: Device not initialized"), Loglevel::DEBUG_L1); throw DeviceNotConnected("Device not initialized"); } dev->m_counters.total_comm_runs++; int status; OutgoingPacket outp; ResponsePacket resp; // POD types can't have non-default constructors outp.initialize(); resp.initialize(); outp.payload = payload; outp.update_CRC(); LOG("-------------------", Loglevel::DEBUG); LOG("Outgoing HID packet:", Loglevel::DEBUG); LOG(static_cast(outp), Loglevel::DEBUG); LOG(std::string("=> ") + std::string(commandid_to_string(static_cast(outp.command_id))), Loglevel::DEBUG_L1); if (!outp.isValid()) { LOG(std::string("Throw: Invalid outgoing packet"), Loglevel::DEBUG_L1); throw DeviceSendingFailure("Invalid outgoing packet"); } bool successful_communication = false; int receiving_retry_counter = 0; int sending_retry_counter = dev->get_retry_sending_count(); while (sending_retry_counter-- > 0) { dev->m_counters.sends_executed++; status = dev->send(&outp); if (status <= 0){ //FIXME early disconnection not yet working properly // LOG("Encountered communication error, disconnecting device", Loglevel::DEBUG_L2); // dev->disconnect(); dev->m_counters.sending_error++; LOG(std::string("Throw: Device error while sending command "), Loglevel::DEBUG_L1); throw DeviceSendingFailure( std::string("Device error while sending command ") + std::to_string(status)); } std::this_thread::sleep_for(dev->get_send_receive_delay()); // FIXME make checks done in device:recv here receiving_retry_counter = dev->get_retry_receiving_count(); int busy_counter = 0; auto retry_timeout = dev->get_retry_timeout(); while (receiving_retry_counter-- > 0) { dev->m_counters.recv_executed++; status = dev->recv(&resp); if (dev->get_device_model() == DeviceModel::STORAGE && resp.command_id >= stick20::CMD_START_VALUE && resp.command_id < stick20::CMD_END_VALUE ) { LOG(std::string("Detected storage device cmd, status: ") + std::to_string(resp.storage_status.device_status), Loglevel::DEBUG_L2); resp.last_command_status = static_cast(stick10::command_status::ok); switch (static_cast(resp.storage_status.device_status)) { case stick20::device_status::idle : case stick20::device_status::ok: resp.device_status = static_cast(stick10::device_status::ok); break; case stick20::device_status::busy: case stick20::device_status::busy_progressbar: //TODO this will be modified later for getting progressbar status resp.device_status = static_cast(stick10::device_status::busy); break; case stick20::device_status::wrong_password: resp.last_command_status = static_cast(stick10::command_status::wrong_password); resp.device_status = static_cast(stick10::device_status::ok); break; case stick20::device_status::no_user_password_unlock: resp.last_command_status = static_cast(stick10::command_status::AES_dec_failed); resp.device_status = static_cast(stick10::device_status::ok); break; default: LOG(std::string("Unknown storage device status, cannot translate: ") + std::to_string(resp.storage_status.device_status), Loglevel::DEBUG); resp.device_status = resp.storage_status.device_status; break; }; } //Some of the commands return wrong CRC, for now skip checking it (TODO list and report) //if (resp.device_status == 0 && resp.last_command_crc == outp.crc && resp.isCRCcorrect()) break; auto CRC_equal_awaited = true; // resp.last_command_crc == outp.crc; if (resp.device_status == static_cast(stick10::device_status::ok) && CRC_equal_awaited && resp.isValid()){ successful_communication = true; break; } if (resp.device_status == static_cast(stick10::device_status::busy)) { dev->m_counters.busy++; if (busy_counter++<10) { receiving_retry_counter++; LOG("Status busy, not decreasing receiving_retry_counter counter: " + std::to_string(receiving_retry_counter), Loglevel::DEBUG_L2); } else { retry_timeout *= 2; retry_timeout = std::min(retry_timeout, 300ms); busy_counter = 0; LOG("Status busy, decreasing receiving_retry_counter counter: " + std::to_string(receiving_retry_counter) + ", current delay:" + std::to_string(retry_timeout.count()), Loglevel::DEBUG); LOG(std::string("Busy retry: status ") + std::to_string(resp.storage_status.device_status) + ", " + std::to_string(retry_timeout.count()) + "ms, counter " + std::to_string(receiving_retry_counter) + ", progress: " + std::to_string(resp.storage_status.progress_bar_value) , Loglevel::DEBUG_L1); } } if (resp.device_status == static_cast(stick10::device_status::busy) && static_cast(resp.storage_status.device_status) == stick20::device_status::busy_progressbar){ successful_communication = true; break; } LOG(std::string("Retry status - dev status, awaited cmd crc, correct packet CRC: ") + std::to_string(resp.device_status) + " " + std::to_string(CRC_equal_awaited) + " " + std::to_string(resp.isCRCcorrect()), Loglevel::DEBUG_L2); if (!resp.isCRCcorrect()) dev->m_counters.wrong_CRC++; if (!CRC_equal_awaited) dev->m_counters.CRC_other_than_awaited++; LOG( "Device is not ready or received packet's last CRC is not equal to sent CRC packet, retrying...", Loglevel::DEBUG_L2); LOG("Invalid incoming HID packet:", Loglevel::DEBUG_L2); LOG(static_cast(resp), Loglevel::DEBUG_L2); dev->m_counters.total_retries++; LOG(".", Loglevel::DEBUG_L1); std::this_thread::sleep_for(retry_timeout); continue; } if (successful_communication) break; LOG(std::string("Resending (outer loop) "), Loglevel::DEBUG_L2); LOG(std::string("sending_retry_counter count: ") + std::to_string(sending_retry_counter), Loglevel::DEBUG); } if(resp.last_command_crc != outp.crc){ LOG(std::string("Accepting response with CRC other than expected ") + "Command ID: " + std::to_string(resp.command_id) + " " + commandid_to_string(static_cast(resp.command_id)) + " " + "Reported by response and expected: " + std::to_string(resp.last_command_crc) + "!=" + std::to_string(outp.crc), Loglevel::WARNING ); } dev->set_last_command_status(resp.last_command_status); // FIXME should be handled on device.recv clear_packet(outp); if (status <= 0) { dev->m_counters.receiving_error++; LOG(std::string("Throw: Device error while executing command "), Loglevel::DEBUG_L1); throw DeviceReceivingFailure( //FIXME replace with CriticalErrorException std::string("Device error while executing command ") + std::to_string(status)); } LOG(std::string("<= ") + std::string( commandid_to_string(static_cast(resp.command_id)) + std::string(" ") + std::to_string(resp.device_status) + std::string(" ") + std::to_string(resp.storage_status.device_status) // + std::to_string( status_translate_command(resp.storage_status.device_status)) ), Loglevel::DEBUG_L1); LOG("Incoming HID packet:", Loglevel::DEBUG); LOG(static_cast(resp), Loglevel::DEBUG); if (dev->get_retry_receiving_count() - receiving_retry_counter > 2) { LOG(std::string("Packet received with receiving_retry_counter count: ") + std::to_string(receiving_retry_counter), Loglevel::DEBUG_L1); } if (resp.device_status == static_cast(stick10::device_status::busy) && static_cast(resp.storage_status.device_status) == stick20::device_status::busy_progressbar){ dev->m_counters.busy_progressbar++; LOG(std::string("Throw: Long operation in progress exception"), Loglevel::DEBUG_L1); throw LongOperationInProgressException( resp.command_id, resp.device_status, resp.storage_status.progress_bar_value); } if (!resp.isValid()) { LOG(std::string("Throw: Invalid incoming packet"), Loglevel::DEBUG_L1); throw InvalidCRCReceived("Invalid incoming packet"); } if (receiving_retry_counter <= 0){ LOG(std::string("Throw: \"Maximum receiving_retry_counter count reached for receiving response from the device!\"" + std::to_string(receiving_retry_counter)), Loglevel::DEBUG_L1); throw DeviceReceivingFailure( "Maximum receiving_retry_counter count reached for receiving response from the device!"); } dev->m_counters.communication_successful++; if (resp.last_command_status != static_cast(stick10::command_status::ok)){ dev->m_counters.command_result_not_equal_0_recv++; LOG(std::string("Throw: CommandFailedException ") + std::to_string(resp.last_command_status), Loglevel::DEBUG_L1); throw CommandFailedException(resp.command_id, resp.last_command_status); } dev->m_counters.command_successful_recv++; if (dev->get_device_model() == DeviceModel::STORAGE && resp.command_id >= stick20::CMD_START_VALUE && resp.command_id < stick20::CMD_END_VALUE ) { dev->m_counters.successful_storage_commands++; } if (!resp.isCRCcorrect()) LOG(std::string("Accepting response from device with invalid CRC. ") + "Command ID: " + std::to_string(resp.command_id) + " " + commandid_to_string(static_cast(resp.command_id)) + " " + "Reported and calculated: " + std::to_string(resp.crc) + "!=" + std::to_string(resp.calculate_CRC()), Loglevel::WARNING ); // See: DeviceResponse return resp; } static ClearingProxy run(std::shared_ptr dev) { command_payload empty_payload; return run(dev, empty_payload); } }; } } #endif