blue-app-eth/src_genericwallet/u2f_transport.c

228 lines
8.7 KiB
C

#ifdef HAVE_U2F
/*
*******************************************************************************
* Portable FIDO U2F implementation
* (c) 2016 Ledger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
********************************************************************************/
#include <stdint.h>
#include <string.h>
#include "u2f_service.h"
#include "u2f_transport.h"
#define U2F_MASK_COMMAND 0x80
#define U2F_COMMAND_HEADER_SIZE 3
static const uint8_t const BROADCAST_CHANNEL[] = {0xff, 0xff, 0xff, 0xff};
static const uint8_t const FORBIDDEN_CHANNEL[] = {0x00, 0x00, 0x00, 0x00};
void u2f_transport_handle(u2f_service_t *service, uint8_t *buffer,
uint16_t size, u2f_transport_media_t media) {
uint16_t channelHeader = (media == U2F_MEDIA_USB ? 4 : 0);
uint8_t channel[4] = {0};
if (media == U2F_MEDIA_USB) {
os_memmove(channel, buffer, 4);
}
// screen_printf("U2F transport\n");
service->packetMedia = media;
u2f_io_open_session();
// If busy, answer immediately
if (service->noReentry) {
if ((service->transportState == U2F_PROCESSING_COMMAND) ||
(service->transportState == U2F_SENDING_RESPONSE)) {
u2f_response_error(service, ERROR_CHANNEL_BUSY, false, channel);
goto error;
}
}
if (size < (1 + channelHeader)) {
// Message to short, abort
u2f_response_error(service, ERROR_PROP_MESSAGE_TOO_SHORT, true,
channel);
goto error;
}
if ((buffer[channelHeader] & U2F_MASK_COMMAND) != 0) {
if (size < (channelHeader + 3)) {
// Message to short, abort
u2f_response_error(service, ERROR_PROP_MESSAGE_TOO_SHORT, true,
channel);
goto error;
}
// If waiting for a continuation on a different channel, reply BUSY
// immediately
if (media == U2F_MEDIA_USB) {
if ((service->pendingContinuation) &&
(os_memcmp(channel, service->lastContinuationChannel, 4) !=
0) &&
(buffer[channelHeader] != U2F_CMD_INIT)) {
u2f_response_error(service, ERROR_CHANNEL_BUSY, false, channel);
goto error;
}
}
// If a command was already sent, and we are not processing a INIT
// command, abort
if ((service->transportState == U2F_HANDLE_SEGMENTED) &&
!((media == U2F_MEDIA_USB) &&
(buffer[channelHeader] == U2F_CMD_INIT))) {
// Unexpected continuation at this stage, abort
u2f_response_error(service, ERROR_INVALID_SEQ, true, channel);
goto error;
}
// Check the length
uint16_t commandLength =
(buffer[channelHeader + 1] << 8) | (buffer[channelHeader + 2]);
if (commandLength > (service->messageBufferSize - 3)) {
// Overflow in message size, abort
u2f_response_error(service, ERROR_INVALID_LEN, true, channel);
goto error;
}
// Check if the command is supported
switch (buffer[channelHeader]) {
case U2F_CMD_PING:
case U2F_CMD_MSG:
if (media == U2F_MEDIA_USB) {
if (u2f_is_channel_broadcast(channel) ||
u2f_is_channel_forbidden(channel)) {
u2f_response_error(service, ERROR_INVALID_CID, true,
channel);
goto error;
}
}
break;
case U2F_CMD_INIT:
if (media != U2F_MEDIA_USB) {
// Unknown command, abort
u2f_response_error(service, ERROR_INVALID_CMD, true, channel);
goto error;
}
break;
default:
// Unknown command, abort
u2f_response_error(service, ERROR_INVALID_CMD, true, channel);
goto error;
}
// Ok, initialize the buffer
os_memmove(service->channel, channel, 4);
service->lastCommandLength = commandLength;
service->expectedContinuationPacket = 0;
os_memmove(service->messageBuffer, buffer + channelHeader,
size - channelHeader);
service->transportOffset = size - channelHeader;
service->transportMedia = media;
} else {
// Continuation
if (size < (channelHeader + 2)) {
// Message to short, abort
u2f_response_error(service, ERROR_PROP_MESSAGE_TOO_SHORT, true,
channel);
goto error;
}
if (media != service->transportMedia) {
// Mixed medias
u2f_response_error(service, ERROR_PROP_MEDIA_MIXED, true, channel);
goto error;
}
if (service->transportState != U2F_HANDLE_SEGMENTED) {
// Unexpected continuation at this stage, abort
// TODO : review the behavior is HID only
if (media == U2F_MEDIA_USB) {
u2f_reset(service, true);
goto error;
} else {
u2f_response_error(service, ERROR_INVALID_SEQ, true, channel);
goto error;
}
}
if (media == U2F_MEDIA_USB) {
// Check the channel
if (os_memcmp(buffer, service->channel, 4) != 0) {
u2f_response_error(service, ERROR_CHANNEL_BUSY, true, channel);
goto error;
}
}
if (buffer[channelHeader] != service->expectedContinuationPacket) {
// Bad continuation packet, abort
u2f_response_error(service, ERROR_INVALID_SEQ, true, channel);
goto error;
}
if ((service->transportOffset + (size - (channelHeader + 1))) >
(service->messageBufferSize - 3)) {
// Overflow, abort
u2f_response_error(service, ERROR_INVALID_LEN, true, channel);
goto error;
}
os_memmove(service->messageBuffer + service->transportOffset,
buffer + channelHeader + 1, size - (channelHeader + 1));
service->transportOffset += size - (channelHeader + 1);
service->expectedContinuationPacket++;
}
// See if we can process the command
if ((media != U2F_MEDIA_USB) &&
(service->transportOffset >
(service->lastCommandLength + U2F_COMMAND_HEADER_SIZE))) {
// Overflow, abort
u2f_response_error(service, ERROR_INVALID_LEN, true, channel);
goto error;
} else if (service->transportOffset >=
(service->lastCommandLength + U2F_COMMAND_HEADER_SIZE)) {
// screen_printf("Process command\n");
service->transportState = U2F_PROCESSING_COMMAND;
service->handleFunction(service, service->messageBuffer, channel);
} else {
// screen_printf("segmented\n");
service->seqTimeout = 0;
service->transportState = U2F_HANDLE_SEGMENTED;
service->pendingContinuation = true;
os_memmove(service->lastContinuationChannel, channel, 4);
u2f_io_close_session();
}
return;
error:
if ((media == U2F_MEDIA_USB) && (service->pendingContinuation) &&
(os_memcmp(channel, service->lastContinuationChannel, 4) == 0)) {
service->pendingContinuation = false;
}
return;
}
void u2f_response_error(u2f_service_t *service, char errorCode, bool reset,
uint8_t *channel) {
uint8_t offset = 0;
os_memset(service->outputBuffer, 0, MAX_SEGMENT_SIZE);
if (service->transportMedia == U2F_MEDIA_USB) {
os_memmove(service->outputBuffer + offset, channel, 4);
offset += 4;
}
service->outputBuffer[offset++] = U2F_STATUS_ERROR;
service->outputBuffer[offset++] = 0x00;
service->outputBuffer[offset++] = 0x01;
service->outputBuffer[offset++] = errorCode;
u2f_send_direct_response_short(service, service->outputBuffer, offset);
if (reset) {
u2f_reset(service, true);
}
}
bool u2f_is_channel_broadcast(uint8_t *channel) {
return (os_memcmp(channel, BROADCAST_CHANNEL, 4) == 0);
}
bool u2f_is_channel_forbidden(uint8_t *channel) {
return (os_memcmp(channel, FORBIDDEN_CHANNEL, 4) == 0);
}
#endif