283 lines
9.9 KiB
C
283 lines
9.9 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 "os.h"
|
||
|
#include "cx.h"
|
||
|
#include "u2f_service.h"
|
||
|
#include "u2f_transport.h"
|
||
|
#include "u2f_processing.h"
|
||
|
|
||
|
#include "btchip_internal.h"
|
||
|
#include "btchip_bagl_extensions.h"
|
||
|
|
||
|
void app_dispatch(void);
|
||
|
void u2f_proxy_response(u2f_service_t *service, unsigned int tx);
|
||
|
|
||
|
static const uint8_t SW_SUCCESS[] = {0x90, 0x00};
|
||
|
static const uint8_t SW_PROOF_OF_PRESENCE_REQUIRED[] = {0x69, 0x85};
|
||
|
static const uint8_t SW_BAD_KEY_HANDLE[] = {0x6A, 0x80};
|
||
|
|
||
|
static const uint8_t VERSION[] = {'U', '2', 'F', '_', 'V', '2', 0x90, 0x00};
|
||
|
static const uint8_t DUMMY_ZERO[] = {0x00};
|
||
|
|
||
|
static const uint8_t SW_UNKNOWN_INSTRUCTION[] = {0x6d, 0x00};
|
||
|
static const uint8_t SW_UNKNOWN_CLASS[] = {0x6e, 0x00};
|
||
|
static const uint8_t SW_WRONG_LENGTH[] = {0x67, 0x00};
|
||
|
static const uint8_t SW_INTERNAL[] = {0x6F, 0x00};
|
||
|
|
||
|
static const uint8_t NOTIFY_USER_PRESENCE_NEEDED[] = {
|
||
|
KEEPALIVE_REASON_TUP_NEEDED};
|
||
|
|
||
|
static const uint8_t PROXY_MAGIC[] = {'B', 'T', 'C'};
|
||
|
|
||
|
#define INIT_U2F_VERSION 0x02
|
||
|
#define INIT_DEVICE_VERSION_MAJOR 0
|
||
|
#define INIT_DEVICE_VERSION_MINOR 1
|
||
|
#define INIT_BUILD_VERSION 0
|
||
|
#define INIT_CAPABILITIES 0x00
|
||
|
|
||
|
#define FIDO_CLA 0x00
|
||
|
#define FIDO_INS_ENROLL 0x01
|
||
|
#define FIDO_INS_SIGN 0x02
|
||
|
#define FIDO_INS_GET_VERSION 0x03
|
||
|
|
||
|
#define FIDO_INS_PROP_GET_COUNTER 0xC0 // U2F_VENDOR_FIRST
|
||
|
|
||
|
#define P1_SIGN_CHECK_ONLY 0x07
|
||
|
#define P1_SIGN_SIGN 0x03
|
||
|
|
||
|
#define U2F_ENROLL_RESERVED 0x05
|
||
|
#define SIGN_USER_PRESENCE_MASK 0x01
|
||
|
|
||
|
#define MAX_SEQ_TIMEOUT_MS 500
|
||
|
#define MAX_KEEPALIVE_TIMEOUT_MS 500
|
||
|
|
||
|
static const uint8_t DUMMY_USER_PRESENCE[] = {SIGN_USER_PRESENCE_MASK};
|
||
|
|
||
|
void u2f_handle_enroll(u2f_service_t *service, uint8_t p1, uint8_t p2,
|
||
|
uint8_t *buffer, uint16_t length) {
|
||
|
(void)p1;
|
||
|
(void)p2;
|
||
|
(void)buffer;
|
||
|
if (length != 32 + 32) {
|
||
|
u2f_send_fragmented_response(service, U2F_CMD_MSG,
|
||
|
(uint8_t *)SW_WRONG_LENGTH,
|
||
|
sizeof(SW_WRONG_LENGTH), true);
|
||
|
return;
|
||
|
}
|
||
|
u2f_send_fragmented_response(service, U2F_CMD_MSG, (uint8_t *)SW_INTERNAL,
|
||
|
sizeof(SW_INTERNAL), true);
|
||
|
}
|
||
|
|
||
|
void u2f_handle_sign(u2f_service_t *service, uint8_t p1, uint8_t p2,
|
||
|
uint8_t *buffer, uint16_t length) {
|
||
|
(void)p1;
|
||
|
(void)p2;
|
||
|
(void)length;
|
||
|
uint8_t keyHandleLength;
|
||
|
uint8_t i;
|
||
|
volatile unsigned int flags = 0;
|
||
|
volatile unsigned int tx = 0;
|
||
|
|
||
|
if (length < 32 + 32 + 1) {
|
||
|
u2f_send_fragmented_response(service, U2F_CMD_MSG,
|
||
|
(uint8_t *)SW_WRONG_LENGTH,
|
||
|
sizeof(SW_WRONG_LENGTH), true);
|
||
|
return;
|
||
|
}
|
||
|
if ((p1 != P1_SIGN_CHECK_ONLY) && (p1 != P1_SIGN_SIGN)) {
|
||
|
u2f_response_error(service, ERROR_PROP_INVALID_PARAMETERS_APDU, true,
|
||
|
service->channel);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
keyHandleLength = buffer[64];
|
||
|
for (i = 0; i < keyHandleLength; i++) {
|
||
|
buffer[65 + i] ^= PROXY_MAGIC[i % sizeof(PROXY_MAGIC)];
|
||
|
}
|
||
|
// Check magic
|
||
|
if (length != (32 + 32 + 1 + 5 + buffer[65 + 4])) {
|
||
|
u2f_send_fragmented_response(service, U2F_CMD_MSG,
|
||
|
(uint8_t *)SW_BAD_KEY_HANDLE,
|
||
|
sizeof(SW_BAD_KEY_HANDLE), true);
|
||
|
}
|
||
|
// Check that it looks like an APDU
|
||
|
os_memmove(G_io_apdu_buffer, buffer + 65, keyHandleLength);
|
||
|
app_dispatch();
|
||
|
if ((btchip_context_D.io_flags & IO_ASYNCH_REPLY) == 0) {
|
||
|
u2f_proxy_response(service, btchip_context_D.outLength);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void u2f_handle_get_version(u2f_service_t *service, uint8_t p1, uint8_t p2,
|
||
|
uint8_t *buffer, uint16_t length) {
|
||
|
// screen_printf("U2F version\n");
|
||
|
(void)p1;
|
||
|
(void)p2;
|
||
|
(void)buffer;
|
||
|
if (length != 0) {
|
||
|
u2f_send_fragmented_response(service, U2F_CMD_MSG,
|
||
|
(uint8_t *)SW_WRONG_LENGTH,
|
||
|
sizeof(SW_WRONG_LENGTH), true);
|
||
|
return;
|
||
|
}
|
||
|
u2f_send_fragmented_response(service, U2F_CMD_MSG, (uint8_t *)VERSION,
|
||
|
sizeof(VERSION), true);
|
||
|
}
|
||
|
|
||
|
void u2f_handle_cmd_init(u2f_service_t *service, uint8_t *buffer,
|
||
|
uint16_t length, uint8_t *channelInit) {
|
||
|
// screen_printf("U2F init\n");
|
||
|
uint8_t channel[4];
|
||
|
(void)length;
|
||
|
uint16_t offset = 0;
|
||
|
if (u2f_is_channel_forbidden(channelInit)) {
|
||
|
u2f_response_error(service, ERROR_INVALID_CID, true, channelInit);
|
||
|
return;
|
||
|
}
|
||
|
if (u2f_is_channel_broadcast(channelInit)) {
|
||
|
cx_rng(channel, 4);
|
||
|
} else {
|
||
|
os_memmove(channel, channelInit, 4);
|
||
|
}
|
||
|
os_memmove(service->messageBuffer + offset, buffer, 8);
|
||
|
offset += 8;
|
||
|
os_memmove(service->messageBuffer + offset, channel, 4);
|
||
|
offset += 4;
|
||
|
service->messageBuffer[offset++] = INIT_U2F_VERSION;
|
||
|
service->messageBuffer[offset++] = INIT_DEVICE_VERSION_MAJOR;
|
||
|
service->messageBuffer[offset++] = INIT_DEVICE_VERSION_MINOR;
|
||
|
service->messageBuffer[offset++] = INIT_BUILD_VERSION;
|
||
|
service->messageBuffer[offset++] = INIT_CAPABILITIES;
|
||
|
if (u2f_is_channel_broadcast(channelInit)) {
|
||
|
os_memset(service->channel, 0xff, 4);
|
||
|
} else {
|
||
|
os_memmove(service->channel, channel, 4);
|
||
|
}
|
||
|
service->keepUserPresence = true;
|
||
|
u2f_send_fragmented_response(service, U2F_CMD_INIT, service->messageBuffer,
|
||
|
offset, true);
|
||
|
// os_memmove(service->channel, channel, 4);
|
||
|
}
|
||
|
|
||
|
void u2f_handle_cmd_ping(u2f_service_t *service, uint8_t *buffer,
|
||
|
uint16_t length) {
|
||
|
// screen_printf("U2F ping\n");
|
||
|
u2f_send_fragmented_response(service, U2F_CMD_PING, buffer, length, true);
|
||
|
}
|
||
|
|
||
|
void u2f_handle_cmd_msg(u2f_service_t *service, uint8_t *buffer,
|
||
|
uint16_t length) {
|
||
|
// screen_printf("U2F msg\n");
|
||
|
uint8_t cla = buffer[0];
|
||
|
uint8_t ins = buffer[1];
|
||
|
uint8_t p1 = buffer[2];
|
||
|
uint8_t p2 = buffer[3];
|
||
|
uint32_t dataLength = (buffer[4] << 16) | (buffer[5] << 8) | (buffer[6]);
|
||
|
if ((dataLength != (uint16_t)(length - 9)) &&
|
||
|
(dataLength != (uint16_t)(length - 7))) { // Le is optional
|
||
|
u2f_send_fragmented_response(service, U2F_CMD_MSG,
|
||
|
(uint8_t *)SW_WRONG_LENGTH,
|
||
|
sizeof(SW_WRONG_LENGTH), true);
|
||
|
return;
|
||
|
}
|
||
|
if (cla != FIDO_CLA) {
|
||
|
u2f_send_fragmented_response(service, U2F_CMD_MSG,
|
||
|
(uint8_t *)SW_UNKNOWN_CLASS,
|
||
|
sizeof(SW_UNKNOWN_CLASS), true);
|
||
|
return;
|
||
|
}
|
||
|
switch (ins) {
|
||
|
case FIDO_INS_ENROLL:
|
||
|
// screen_printf("enroll\n");
|
||
|
u2f_handle_enroll(service, p1, p2, buffer + 7, dataLength);
|
||
|
break;
|
||
|
case FIDO_INS_SIGN:
|
||
|
// screen_printf("sign\n");
|
||
|
u2f_handle_sign(service, p1, p2, buffer + 7, dataLength);
|
||
|
break;
|
||
|
case FIDO_INS_GET_VERSION:
|
||
|
// screen_printf("version\n");
|
||
|
u2f_handle_get_version(service, p1, p2, buffer + 7, dataLength);
|
||
|
break;
|
||
|
default:
|
||
|
// screen_printf("unsupported\n");
|
||
|
u2f_send_fragmented_response(service, U2F_CMD_MSG,
|
||
|
(uint8_t *)SW_UNKNOWN_INSTRUCTION,
|
||
|
sizeof(SW_UNKNOWN_INSTRUCTION), true);
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void u2f_process_message(u2f_service_t *service, uint8_t *buffer,
|
||
|
uint8_t *channel) {
|
||
|
uint8_t cmd = buffer[0];
|
||
|
uint16_t length = (buffer[1] << 8) | (buffer[2]);
|
||
|
switch (cmd) {
|
||
|
case U2F_CMD_INIT:
|
||
|
u2f_handle_cmd_init(service, buffer + 3, length, channel);
|
||
|
break;
|
||
|
case U2F_CMD_PING:
|
||
|
service->pendingContinuation = false;
|
||
|
u2f_handle_cmd_ping(service, buffer + 3, length);
|
||
|
break;
|
||
|
case U2F_CMD_MSG:
|
||
|
service->pendingContinuation = false;
|
||
|
if (!service->noReentry && service->runningCommand) {
|
||
|
u2f_response_error(service, ERROR_CHANNEL_BUSY, false,
|
||
|
service->channel);
|
||
|
break;
|
||
|
}
|
||
|
service->runningCommand = true;
|
||
|
u2f_handle_cmd_msg(service, buffer + 3, length);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void u2f_timeout(u2f_service_t *service) {
|
||
|
service->timerNeedGeneralStatus = true;
|
||
|
if ((service->transportMedia == U2F_MEDIA_USB) &&
|
||
|
(service->pendingContinuation)) {
|
||
|
service->seqTimeout += service->timerInterval;
|
||
|
if (service->seqTimeout > MAX_SEQ_TIMEOUT_MS) {
|
||
|
service->pendingContinuation = false;
|
||
|
u2f_response_error(service, ERROR_MSG_TIMEOUT, true,
|
||
|
service->lastContinuationChannel);
|
||
|
}
|
||
|
}
|
||
|
#ifdef HAVE_BLE
|
||
|
if ((service->transportMedia == U2F_MEDIA_BLE) &&
|
||
|
(service->requireKeepalive)) {
|
||
|
service->keepaliveTimeout += service->timerInterval;
|
||
|
if (service->keepaliveTimeout > MAX_KEEPALIVE_TIMEOUT_MS) {
|
||
|
service->keepaliveTimeout = 0;
|
||
|
u2f_send_fragmented_response(service, U2F_CMD_KEEPALIVE,
|
||
|
(uint8_t *)NOTIFY_USER_PRESENCE_NEEDED,
|
||
|
sizeof(NOTIFY_USER_PRESENCE_NEEDED),
|
||
|
false);
|
||
|
}
|
||
|
}
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
#endif
|