blue-app-openpgp-card/src/gpg_io.c
Cédric Mesnil 8430858dfb Async interaction and PIN on screen
Add PIN on screen
Fix strings declarations and usages to avoid PIC fails
Fix IO for Async interaction
Move UX code in dedicated file
2017-04-12 12:11:00 +02:00

345 lines
10 KiB
C

/* Copyright 2017 Cedric Mesnil <cslashm@gmail.com>, Ledger SAS
*
* 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 "os.h"
#include "cx.h"
#include "gpg_types.h"
#include "gpg_api.h"
#include "gpg_vars.h"
/*
* io_buff: contains current message part
* io_off: offset in current message part
* io_length: length of current message part
*/
/* ----------------------------------------------------------------------- */
/* MISC */
/* ----------------------------------------------------------------------- */
void gpg_io_set_offset(unsigned int offset) {
if (offset == IO_OFFSET_END) {
G_gpg_vstate.io_offset = G_gpg_vstate.io_length;
}
else if (offset == IO_OFFSET_MARK) {
G_gpg_vstate.io_offset = G_gpg_vstate.io_mark;
}
else if (offset < G_gpg_vstate.io_length) {
G_gpg_vstate.io_offset = G_gpg_vstate.io_length;
}
else {
THROW(ERROR_IO_OFFSET);
}
}
void gpg_io_mark() {
G_gpg_vstate.io_mark = G_gpg_vstate.io_offset;
}
void gpg_io_inserted(unsigned int len) {
G_gpg_vstate.io_offset += len;
G_gpg_vstate.io_length += len;
}
void gpg_io_discard(int clear) {
G_gpg_vstate.io_length = 0;
G_gpg_vstate.io_offset = 0;
G_gpg_vstate.io_mark = 0;
if (clear) {
gpg_io_clear();
}
}
void gpg_io_clear() {
os_memset(G_gpg_vstate.work.io_buffer, 0 , GPG_IO_BUFFER_LENGTH);
}
/* ----------------------------------------------------------------------- */
/* INSERT data to be sent */
/* ----------------------------------------------------------------------- */
void gpg_io_hole(unsigned int sz) {
if ((G_gpg_vstate.io_length + sz) > GPG_IO_BUFFER_LENGTH) {
THROW(ERROR_IO_FULL);
}
os_memmove(G_gpg_vstate.work.io_buffer+G_gpg_vstate.io_offset+sz,
G_gpg_vstate.work.io_buffer+G_gpg_vstate.io_offset,
G_gpg_vstate.io_length-G_gpg_vstate.io_offset);
G_gpg_vstate.io_length += sz;
}
void gpg_io_insert(unsigned char const *buff, unsigned int len) {
gpg_io_hole(len);
os_memmove(G_gpg_vstate.work.io_buffer+G_gpg_vstate.io_offset, buff, len);
G_gpg_vstate.io_offset += len;
}
void gpg_io_insert_u32(unsigned int v32) {
gpg_io_hole(4);
G_gpg_vstate.work.io_buffer[G_gpg_vstate.io_offset+0] = v32>>24;
G_gpg_vstate.work.io_buffer[G_gpg_vstate.io_offset+1] = v32>>16;
G_gpg_vstate.work.io_buffer[G_gpg_vstate.io_offset+2] = v32>>8;
G_gpg_vstate.work.io_buffer[G_gpg_vstate.io_offset+3] = v32>>0;
G_gpg_vstate.io_offset += 4;
}
void gpg_io_insert_u24(unsigned int v24) {
gpg_io_hole(3);
G_gpg_vstate.work.io_buffer[G_gpg_vstate.io_offset+0] = v24>>16;
G_gpg_vstate.work.io_buffer[G_gpg_vstate.io_offset+1] = v24>>8;
G_gpg_vstate.work.io_buffer[G_gpg_vstate.io_offset+2] = v24>>0;
G_gpg_vstate.io_offset += 3;
}
void gpg_io_insert_u16(unsigned int v16) {
gpg_io_hole(2);
G_gpg_vstate.work.io_buffer[G_gpg_vstate.io_offset+0] = v16>>8;
G_gpg_vstate.work.io_buffer[G_gpg_vstate.io_offset+1] = v16>>0;
G_gpg_vstate.io_offset += 2;
}
void gpg_io_insert_u8(unsigned int v8) {
gpg_io_hole(1);
G_gpg_vstate.work.io_buffer[G_gpg_vstate.io_offset+0] = v8;
G_gpg_vstate.io_offset += 1;
}
void gpg_io_insert_t(unsigned int T) {
if (T &0xFF00) {
gpg_io_insert_u16(T);
} else {
gpg_io_insert_u8(T);
}
}
void gpg_io_insert_tl(unsigned int T, unsigned int L) {
gpg_io_insert_t(T);
if (L < 128) {
gpg_io_insert_u8(L);
} else if (L < 256) {
gpg_io_insert_u16(0x8100|L);
} else {
gpg_io_insert_u8(0x82);
gpg_io_insert_u16(L);
}
}
void gpg_io_insert_tlv(unsigned int T, unsigned int L, unsigned char const *V) {
gpg_io_insert_tl(T,L);
gpg_io_insert(V, L);
}
/* ----------------------------------------------------------------------- */
/* FECTH data from received buffer */
/* ----------------------------------------------------------------------- */
void gpg_io_fetch_buffer(unsigned char* buffer, unsigned int len) {
os_memmove(buffer, G_gpg_vstate.work.io_buffer+G_gpg_vstate.io_offset, len);
G_gpg_vstate.io_offset += len;
}
unsigned int gpg_io_fetch_u32() {
unsigned int v32;
v32 = ( (G_gpg_vstate.work.io_buffer[G_gpg_vstate.io_offset+0] << 24) |
(G_gpg_vstate.work.io_buffer[G_gpg_vstate.io_offset+1] << 16) |
(G_gpg_vstate.work.io_buffer[G_gpg_vstate.io_offset+2] << 8) |
(G_gpg_vstate.work.io_buffer[G_gpg_vstate.io_offset+3] << 0) );
G_gpg_vstate.io_offset += 4;
return v32;
}
unsigned int gpg_io_fetch_u16() {
unsigned int v16;
v16 = ( (G_gpg_vstate.work.io_buffer[G_gpg_vstate.io_offset+0] << 8) |
(G_gpg_vstate.work.io_buffer[G_gpg_vstate.io_offset+1] << 0) );
G_gpg_vstate.io_offset += 2;
return v16;
}
unsigned int gpg_io_fetch_u8() {
unsigned int v8;
v8 = G_gpg_vstate.work.io_buffer[G_gpg_vstate.io_offset] ;
G_gpg_vstate.io_offset += 1;
return v8;
}
int gpg_io_fetch_t(unsigned int *T) {
*T = G_gpg_vstate.work.io_buffer[G_gpg_vstate.io_offset];
G_gpg_vstate.io_offset++;
if ((*T & 0x1F) == 0x1F) {
*T = (*T << 8) | G_gpg_vstate.work.io_buffer[G_gpg_vstate.io_offset];
G_gpg_vstate.io_offset++;
}
return 0;
}
int gpg_io_fetch_l(unsigned int *L) {
*L = G_gpg_vstate.work.io_buffer[G_gpg_vstate.io_offset];
if ((*L & 0x80) != 0) {
*L &= 0x7F;
if (*L == 1) {
*L = G_gpg_vstate.work.io_buffer[G_gpg_vstate.io_offset+1];
G_gpg_vstate.io_offset += 2;
} else if (*L == 2) {
*L = (G_gpg_vstate.work.io_buffer[G_gpg_vstate.io_offset+1] << 8) | G_gpg_vstate.work.io_buffer[G_gpg_vstate.io_offset+2] ;
G_gpg_vstate.io_offset += 3 ;
} else {
*L = -1;
}
} else {
G_gpg_vstate.io_offset += 1 ;
}
return 0;
}
int gpg_io_fetch_tl(unsigned int *T, unsigned int *L) {
gpg_io_fetch_t(T);
gpg_io_fetch_l(L);
return 0;
}
int gpg_io_fetch_nv(unsigned char* buffer, int len) {
gpg_nvm_write(buffer, G_gpg_vstate.work.io_buffer+G_gpg_vstate.io_offset, len);
G_gpg_vstate.io_offset += len;
return len;
}
int gpg_io_fetch(unsigned char* buffer, int len) {
if (buffer) {
os_memmove(buffer, G_gpg_vstate.work.io_buffer+G_gpg_vstate.io_offset, len);
}
G_gpg_vstate.io_offset += len;
return len;
}
/* ----------------------------------------------------------------------- */
/* REAL IO */
/* ----------------------------------------------------------------------- */
#define MAX_OUT GPG_APDU_LENGTH
int gpg_io_do(unsigned int io_flags) {
//if pending input chaining
if (G_gpg_vstate.io_cla & 0x01) {
goto in_chaining;
}
if (io_flags & IO_ASYNCH_REPLY) {
// if IO_ASYNCH_REPLY has been set,
// gpg_io_exchange will return when IO_RETURN_AFTER_TX will set in ui
gpg_io_exchange(CHANNEL_APDU | IO_ASYNCH_REPLY, 0);
} else {
// --- full out chaining ---
G_gpg_vstate.io_offset = 0;
while(G_gpg_vstate.io_length > MAX_OUT) {
unsigned int tx,xx;
//send chunk
tx = MAX_OUT-2;
os_memmove(G_io_apdu_buffer, G_gpg_vstate.work.io_buffer+G_gpg_vstate.io_offset, tx);
G_gpg_vstate.io_length -= tx;
G_gpg_vstate.io_offset += tx;
G_io_apdu_buffer[tx] = 0x61;
if (G_gpg_vstate.io_length > MAX_OUT-2) {
xx = MAX_OUT-2;
} else {
xx = G_gpg_vstate.io_length-2;
}
G_io_apdu_buffer[tx+1] = xx;
gpg_io_exchange(CHANNEL_APDU, tx+2);
//check get response
if ((G_io_apdu_buffer[0] != 0x00) ||
(G_io_apdu_buffer[1] != 0xc0) ||
(G_io_apdu_buffer[2] != 0x00) ||
(G_io_apdu_buffer[3] != 0x00) ) {
THROW(SW_COMMAND_NOT_ALLOWED);
}
}
os_memmove(G_io_apdu_buffer, G_gpg_vstate.work.io_buffer+G_gpg_vstate.io_offset, G_gpg_vstate.io_length);
if (io_flags & IO_RETURN_AFTER_TX) {
gpg_io_exchange(CHANNEL_APDU |IO_RETURN_AFTER_TX, G_gpg_vstate.io_length);
return 0;
} else {
gpg_io_exchange(CHANNEL_APDU, G_gpg_vstate.io_length);
}
}
//--- full in chaining ---
G_gpg_vstate.io_offset = 0;
G_gpg_vstate.io_length = 0;
G_gpg_vstate.io_cla = G_io_apdu_buffer[0];
G_gpg_vstate.io_ins = G_io_apdu_buffer[1];
G_gpg_vstate.io_p1 = G_io_apdu_buffer[2];
G_gpg_vstate.io_p2 = G_io_apdu_buffer[3];
G_gpg_vstate.io_lc = 0;
G_gpg_vstate.io_le = 0;
switch (G_gpg_vstate.io_ins) {
case INS_GET_DATA:
case INS_GET_RESPONSE:
case INS_TERMINATE_DF:
case INS_ACTIVATE_FILE:
G_gpg_vstate.io_le = G_io_apdu_buffer[4];
break;
case INS_GET_CHALLENGE:
if (G_gpg_vstate.io_p1 == 0) {
break;
}
case INS_VERIFY:
case INS_CHANGE_REFERENCE_DATA:
if (G_io_apdu_buffer[4] == 0) {
break;
}
goto _default;
default:
_default:
G_gpg_vstate.io_lc = G_io_apdu_buffer[4];
os_memmove(G_gpg_vstate.work.io_buffer, G_io_apdu_buffer+5, G_gpg_vstate.io_lc);
G_gpg_vstate.io_length = G_gpg_vstate.io_lc;
break;
}
while(G_gpg_vstate.io_cla & 0x10) {
G_io_apdu_buffer[0] = 0x90;
G_io_apdu_buffer[1] = 0x00;
gpg_io_exchange(CHANNEL_APDU, 2);
in_chaining:
if (((G_io_apdu_buffer[0] & 0xEF) != (G_gpg_vstate.io_cla& 0xEF)) ||
(G_io_apdu_buffer[1] != G_gpg_vstate.io_ins) ||
(G_io_apdu_buffer[2] != G_gpg_vstate.io_p1) ||
(G_io_apdu_buffer[3] != G_gpg_vstate.io_p2) ) {
THROW(SW_COMMAND_NOT_ALLOWED);
}
G_gpg_vstate.io_cla = G_io_apdu_buffer[0];
G_gpg_vstate.io_lc = G_io_apdu_buffer[4];
if ((G_gpg_vstate.io_length + G_gpg_vstate.io_lc) > GPG_IO_BUFFER_LENGTH) {
return 1;
}
os_memmove(G_gpg_vstate.work.io_buffer+G_gpg_vstate.io_length, G_io_apdu_buffer+5, G_gpg_vstate.io_lc);
G_gpg_vstate.io_length += G_gpg_vstate.io_lc;
}
return 0;
}