diff --git a/ChangeLog b/ChangeLog index c594045..309101f 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,7 +1,22 @@ 2011-01-27 NIIBE Yutaka - * src/openpgp-do.c (gpg_do_table): Exclude GPG_DO_CH_CERTIFICATE - for now. + * src/usb-icc.c (res_APDU_pointer): New. + (icc_handle_data, USBthread): Handle res_APDU_pointer. + + * src/openpgp.h (GPG_COMMAND_NOT_ALLOWED): New. + + * src/openpgp.c (INS_UPDATE_BINARY, FILE_EF_CH_CERTIFICATE) + (FILE_EF_RANDOM, cmd_update_binary): New. + (process_command_apdu): Initialize res_APDU_pointer. + + * src/openpgp-do.c (gpg_do_get_data): Handle GPG_DO_CH_CERTIFICATE. + + * src/gnuk.ld.in (.gnuk_ch_certificate): New. + + * src/flash.c (flash_check_blank, flash_erase_binary) + (flash_write_binary): New. + + * src/openpgp-do.c (gpg_do_table): Exclude GPG_DO_CH_CERTIFICATE. * src/openpgp.c (cmd_reset_user_password): Add PINPAD_SUPPORT. diff --git a/NEWS b/NEWS index 30743a6..5d888fe 100644 --- a/NEWS +++ b/NEWS @@ -4,6 +4,8 @@ Gnuk NEWS - User visible changes Released 2011-01-XX, by NIIBE Yutaka +** Card Holder Certificate is supported. + ** Better interoperability to OpenSC. Gnuk is not yet supported by OpenSC, but it could be. With the changes in Gnuk, it could be relatively easily possible to support diff --git a/src/flash.c b/src/flash.c index 1e7d1a2..193b725 100644 --- a/src/flash.c +++ b/src/flash.c @@ -1,7 +1,7 @@ /* * flash.c -- Data Objects (DO) and GPG Key handling on Flash ROM * - * Copyright (C) 2010 Free Software Initiative of Japan + * Copyright (C) 2010, 2011 Free Software Initiative of Japan * Author: NIIBE Yutaka * * This file is a part of Gnuk, a GnuPG USB Token implementation. @@ -25,8 +25,8 @@ * We assume single DO size is less than 256. * * NOTE: When we will support "Card holder certificate" (which size is - * larger than 256), it will not be put into data pool, but will - * be implemented by its own flash page. + * larger than 256), it is not put into data pool, but is + * implemented by its own flash page(s). */ #include "config.h" @@ -167,7 +167,7 @@ static const uint8_t *keystore_pool; static uint8_t *last_p; static const uint8_t *keystore; -/* The first halfward is generation for the data page (little endian) */ +/* The first halfword is generation for the data page (little endian) */ const uint8_t const flash_data[4] __attribute__ ((section (".gnuk_data"))) = { 0x01, 0x00, 0xff, 0xff }; @@ -300,7 +300,7 @@ flash_do_write_internal (const uint8_t *p, int nr, const uint8_t *data, int len) flash_warning ("DO WRITE ERROR"); addr += 2; - for (i = 0; i < len/2; i ++) + for (i = 0; i < len/2; i++) { hw = data[i*2] | (data[i*2+1]<<8); if (flash_program_halfword (addr, hw) != FLASH_COMPLETE) @@ -581,3 +581,71 @@ flash_cnt123_clear (const uint8_t **addr_p) flash_program_halfword ((uint32_t)p, 0); *addr_p = NULL; } + + +static int +flash_check_blank (const uint8_t *page, int size) +{ + const uint8_t *p; + + for (p = page; p < page + size; p++) + if (*p != 0xff) + return 0; + + return 1; +} + + +#define FLASH_CH_CERTIFICATE_SIZE 2048 +int +flash_erase_binary (uint8_t file_id) +{ + const uint8_t *p = &ch_certificate_start; + + if (file_id == FILEID_CH_CERTIFICATE) + { + if (flash_check_blank (p, FLASH_CH_CERTIFICATE_SIZE) == 0) + { + flash_erase_page ((uint32_t)p); +#if FLASH_CH_CERTIFICATE_SIZE > FLASH_PAGE_SIZE + flash_erase_page ((uint32_t)p + FLASH_PAGE_SIZE); +#endif + } + + return 0; + } + else + return -1; +} + + +int +flash_write_binary (uint8_t file_id, const uint8_t *data, + uint16_t len, uint16_t offset) +{ + if (file_id == FILEID_CH_CERTIFICATE) + { + if (offset + len > FLASH_CH_CERTIFICATE_SIZE || (offset&1) || (len&1)) + return -1; + else + { + const uint8_t *p = &ch_certificate_start; + uint16_t hw; + uint32_t addr; + int i; + + addr = (uint32_t)p + offset; + for (i = 0; i < len/2; i++) + { + hw = data[i*2] | (data[i*2+1]<<8); + if (flash_program_halfword (addr, hw) != FLASH_COMPLETE) + flash_warning ("DO WRITE ERROR"); + addr += 2; + } + + return 0; + } + } + else + return -1; +} diff --git a/src/gnuk.h b/src/gnuk.h index 114a3c7..c363271 100644 --- a/src/gnuk.h +++ b/src/gnuk.h @@ -36,6 +36,7 @@ extern void *memmove(void *dest, const void *src, size_t n); extern int icc_data_size; #define cmd_APDU_size icc_data_size extern int res_APDU_size; +extern uint8_t *res_APDU_pointer; /* USB buffer size of LL (Low-level): size of single Bulk transaction */ #define USB_LL_BUF_SIZE 64 @@ -115,6 +116,14 @@ extern void flash_clear_halfword (uint32_t addr); extern void flash_increment_counter (uint8_t counter_tag_nr); extern void flash_reset_counter (uint8_t counter_tag_nr); +#define FILEID_CH_CERTIFICATE 0 +#define FILEID_RANDOM 1 +extern int flash_erase_binary (uint8_t file_id); +extern int flash_write_binary (uint8_t file_id, const uint8_t *data, uint16_t len, uint16_t offset); + +/* Linker set this symbol */ +extern uint8_t ch_certificate_start; + #define KEY_MAGIC_LEN 8 #define KEY_CONTENT_LEN 256 /* p and q */ #define GNUK_MAGIC "Gnuk KEY" diff --git a/src/gnuk.ld.in b/src/gnuk.ld.in index c968c4e..a296369 100644 --- a/src/gnuk.ld.in +++ b/src/gnuk.ld.in @@ -121,6 +121,14 @@ SECTIONS . = ALIGN (@FLASH_PAGE_SIZE@); } > flash =0xffffffff + .gnuk_ch_certificate : + { + . = ALIGN (@FLASH_PAGE_SIZE@); + ch_certificate_start = .; + . += 1920; + . = ALIGN (@FLASH_PAGE_SIZE@); + } > flash =0xffffffff + .gnuk_flash : { _data_pool = .; diff --git a/src/openpgp-do.c b/src/openpgp-do.c index 07b46fb..6f9a1b1 100644 --- a/src/openpgp-do.c +++ b/src/openpgp-do.c @@ -929,8 +929,8 @@ gpg_do_table[] = { { GPG_DO_KEY_IMPORT, DO_PROC_WRITE, AC_NEVER, AC_ADMIN_AUTHORIZED, proc_key_import }, #if 0 - /* Card holder certificate: Not supported yet */ - { GPG_DO_CH_CERTIFICATE, DO_PROC_READWRITE, AC_NEVER, AC_NEVER, NULL }, + /* Card holder certificate */ + { GPG_DO_CH_CERTIFICATE, DO_PROC_READ, AC_ALWAYS, AC_NEVER, NULL }, #endif }; @@ -1200,27 +1200,40 @@ copy_do (const struct do_table_entry *do_p, int with_tag) void gpg_do_get_data (uint16_t tag, int with_tag) { - const struct do_table_entry *do_p = get_do_entry (tag); - - res_p = res_APDU; - - DEBUG_INFO (" "); - DEBUG_SHORT (tag); - - if (do_p) + if (tag == GPG_DO_CH_CERTIFICATE) { - if (copy_do (do_p, with_tag) < 0) - /* Overwriting partially written result */ - GPG_SECURITY_FAILURE (); - else + res_APDU_pointer = &ch_certificate_start; + res_APDU_size = (res_APDU_pointer[2] << 8) | res_APDU_pointer[3]; + if (res_APDU_size == 0xffff) { - *res_p++ = 0x90; - *res_p++ = 0x00; - res_APDU_size = res_p - res_APDU; + res_APDU_pointer = NULL; + GPG_NO_RECORD (); } } else - GPG_NO_RECORD (); + { + const struct do_table_entry *do_p = get_do_entry (tag); + + res_p = res_APDU; + + DEBUG_INFO (" "); + DEBUG_SHORT (tag); + + if (do_p) + { + if (copy_do (do_p, with_tag) < 0) + /* Overwriting partially written result */ + GPG_SECURITY_FAILURE (); + else + { + *res_p++ = 0x90; + *res_p++ = 0x00; + res_APDU_size = res_p - res_APDU; + } + } + else + GPG_NO_RECORD (); + } } void diff --git a/src/openpgp.c b/src/openpgp.c index f90e176..5760516 100644 --- a/src/openpgp.c +++ b/src/openpgp.c @@ -39,6 +39,7 @@ #define INS_SELECT_FILE 0xa4 #define INS_READ_BINARY 0xb0 #define INS_GET_DATA 0xca +#define INS_UPDATE_BINARY 0xd6 #define INS_PUT_DATA 0xda #define INS_PUT_DATA_ODD 0xdb /* For key import */ @@ -75,6 +76,8 @@ write_res_apdu (const uint8_t *p, int len, uint8_t sw1, uint8_t sw2) #define FILE_MF 2 #define FILE_EF_DIR 3 #define FILE_EF_SERIAL 4 +#define FILE_EF_CH_CERTIFICATE 5 +#define FILE_EF_RANDOM 6 static uint8_t file_selection; @@ -632,7 +635,7 @@ cmd_read_binary (void) static void cmd_select_file (void) { - if (cmd_APDU[2] == 4) /* Selection by DF name */ + if (cmd_APDU[2] == 4) /* Selection by DF name: it must be OpenPGP card */ { DEBUG_INFO (" - select DF by name\r\n"); @@ -654,8 +657,7 @@ cmd_select_file (void) } } else if (cmd_APDU[4] == 2 - && cmd_APDU[5] == 0x2f - && cmd_APDU[6] == 0x02) + && cmd_APDU[5] == 0x2f && cmd_APDU[6] == 0x02) { DEBUG_INFO (" - select 0x2f02 EF\r\n"); /* @@ -665,8 +667,7 @@ cmd_select_file (void) file_selection = FILE_EF_SERIAL; } else if (cmd_APDU[4] == 2 - && cmd_APDU[5] == 0x3f - && cmd_APDU[6] == 0x00) + && cmd_APDU[5] == 0x3f && cmd_APDU[6] == 0x00) { DEBUG_INFO (" - select ROOT MF\r\n"); if (cmd_APDU[3] == 0x0c) @@ -682,6 +683,7 @@ cmd_select_file (void) } file_selection = FILE_MF; + ac_fini (); /* Reset authentication */ } else { @@ -838,6 +840,77 @@ cmd_internal_authenticate (void) DEBUG_INFO ("INTERNAL AUTHENTICATE done.\r\n"); } + +static void +cmd_update_binary (void) +{ + int len = cmd_APDU[4]; + int data_start = 5; + uint16_t offset; + int r; + + if (len == 0) + { + len = (cmd_APDU[5]<<8) | cmd_APDU[6]; + data_start = 7; + } + + DEBUG_INFO (" - UPDATE BINARY\r\n"); + + if (gpg_passwd_locked (PW_ERR_PW3) || !ac_check_status (AC_ADMIN_AUTHORIZED)) + { + DEBUG_INFO ("security error."); + GPG_SECURITY_FAILURE (); + return; + } + + if ((cmd_APDU[2] & 0x80)) + if ((cmd_APDU[2] & 0x7f) <= 0x01) + { + file_selection = FILE_EF_CH_CERTIFICATE + (cmd_APDU[2] & 0x7f); + r = flash_erase_binary (file_selection - FILE_EF_CH_CERTIFICATE); + if (r < 0) + { + DEBUG_INFO ("memory error.\r\n"); + GPG_MEMORY_FAILURE (); + return; + } + + offset = 0; + } + else + { + GPG_NO_FILE (); + return; + } + else + { + if (file_selection != FILE_EF_CH_CERTIFICATE + && file_selection != FILE_EF_RANDOM) + { + GPG_COMMAND_NOT_ALLOWED (); + return; + } + + offset = (cmd_APDU[2] << 8) | cmd_APDU[3]; + } + + DEBUG_SHORT (len); + DEBUG_SHORT (offset); + + r = flash_write_binary (file_selection - FILE_EF_CH_CERTIFICATE, + &cmd_APDU[data_start], len, offset); + if (r < 0) + { + DEBUG_INFO ("memory error.\r\n"); + GPG_MEMORY_FAILURE (); + return; + } + + DEBUG_INFO ("UPDATE BINARY done.\r\n"); +} + + struct command { uint8_t command; @@ -854,6 +927,7 @@ const struct command cmds[] = { { INS_SELECT_FILE, cmd_select_file }, { INS_READ_BINARY, cmd_read_binary }, { INS_GET_DATA, cmd_get_data }, + { INS_UPDATE_BINARY, cmd_update_binary }, /* Not in OpenPGP card protocol */ { INS_PUT_DATA, cmd_put_data }, { INS_PUT_DATA_ODD, cmd_put_data }, }; @@ -869,6 +943,8 @@ process_command_apdu (void) if (cmds[i].command == cmd) break; + res_APDU_pointer = NULL; /* default */ + if (i < NUM_CMDS) cmds[i].cmd_handler (); else diff --git a/src/openpgp.h b/src/openpgp.h index fd4175d..035f026 100644 --- a/src/openpgp.h +++ b/src/openpgp.h @@ -1,6 +1,7 @@ #define GPG_MEMORY_FAILURE() write_res_apdu (NULL, 0, 0x65, 0x81) #define GPG_SECURITY_FAILURE() write_res_apdu (NULL, 0, 0x69, 0x82) #define GPG_SECURITY_AUTH_BLOCKED() write_res_apdu (NULL, 0, 0x69, 0x83) +#define GPG_COMMAND_NOT_ALLOWED() write_res_apdu (NULL, 0, 0x69, 0x86) #define GPG_NO_FILE() write_res_apdu (NULL, 0, 0x6a, 0x82) #define GPG_NO_RECORD() write_res_apdu (NULL, 0, 0x6a, 0x88) #define GPG_BAD_P0_P1() write_res_apdu (NULL, 0, 0x6b, 0x00) diff --git a/src/usb-icc.c b/src/usb-icc.c index 11b03d7..189e2ec 100644 --- a/src/usb-icc.c +++ b/src/usb-icc.c @@ -385,6 +385,7 @@ icc_power_off (void) } int res_APDU_size; +uint8_t *res_APDU_pointer; static void icc_send_data_block (int len, uint8_t status, uint8_t chain) @@ -608,8 +609,16 @@ icc_handle_data (void) { if (icc_header->param == 0x10) { - memmove (res_APDU, res_APDU+ICC_RESPONSE_MSG_DATA_SIZE, - res_APDU_size); + if (res_APDU_pointer != NULL) + { + memcpy (res_APDU, res_APDU_pointer, + ICC_RESPONSE_MSG_DATA_SIZE); + res_APDU_pointer += ICC_RESPONSE_MSG_DATA_SIZE; + } + else + memmove (res_APDU, res_APDU+ICC_RESPONSE_MSG_DATA_SIZE, + res_APDU_size); + if (res_APDU_size <= ICC_RESPONSE_MSG_DATA_SIZE) { icc_send_data_block (res_APDU_size, 0, 0x02); @@ -689,6 +698,9 @@ USBthread (void *arg) { if (icc_state == ICC_STATE_EXECUTE) { + if (res_APDU_pointer != NULL) + memcpy (res_APDU, res_APDU_pointer, ICC_RESPONSE_MSG_DATA_SIZE); + if (res_APDU_size <= ICC_RESPONSE_MSG_DATA_SIZE) { icc_send_data_block (res_APDU_size, 0, 0); @@ -698,6 +710,7 @@ USBthread (void *arg) { icc_send_data_block (ICC_RESPONSE_MSG_DATA_SIZE, 0, 0x01); res_APDU_size -= ICC_RESPONSE_MSG_DATA_SIZE; + res_APDU_pointer += ICC_RESPONSE_MSG_DATA_SIZE; icc_state = ICC_STATE_SEND; } }