From 6893a0bc6470eb8233c07b5618cf511074529c15 Mon Sep 17 00:00:00 2001 From: NIIBE Yutaka Date: Fri, 28 Jan 2011 17:38:52 +0900 Subject: [PATCH] add support of card holder certificate --- ChangeLog | 12 ++ doc/HACKING | 7 +- doc/NOTES | 12 +- src/openpgp-do.c | 5 +- src/openpgp.c | 6 +- src/usb-icc.c | 47 +++---- tool/dfuse.py | 2 +- tool/dump_mem.py | 22 ++++ tool/gnuk_update_binary.py | 246 +++++++++++++++++++++++++++++++++++++ 9 files changed, 321 insertions(+), 38 deletions(-) create mode 100755 tool/gnuk_update_binary.py diff --git a/ChangeLog b/ChangeLog index 309101f..2932902 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,15 @@ +2011-01-28 NIIBE Yutaka + + * tool/gnuk_update_binary.py: New. + + * src/openpgp-do.c (gpg_do_get_data): Fix length adding two for + status word at the end and adding four for the tag and the length. + + * src/usb-icc.c (icc_handle_data): Fix decrementing res_APDU_size. + (icc_power_off): Status should be the one *after* power off. + + * src/openpgp.c (cmd_update_binary): Fix return code. + 2011-01-27 NIIBE Yutaka * src/usb-icc.c (res_APDU_pointer): New. diff --git a/doc/HACKING b/doc/HACKING index c4cb1b1..6246e76 100644 --- a/doc/HACKING +++ b/doc/HACKING @@ -29,11 +29,10 @@ It would be good not to use malloc. Get it from FSFE. -* [Partially DONE] Serial number +* [Mostly DONE] Serial number -Currently, aid[] in openpgp-do.c has serial number 00000001. -It would be good to generate (random) number at compile time. -Use same serial number for OpenPGPcard and USB serial number. +The AID of the card contains serial number. It should be unique. USB +serial number should be unique to identify different tokens, too. * Flash ROM recover from shutdown diff --git a/doc/NOTES b/doc/NOTES index 86626db..457ee66 100644 --- a/doc/NOTES +++ b/doc/NOTES @@ -4,15 +4,15 @@ USB communication * No command chaining, but extended APDU and extended Lc and Le. I think that this keep the code simple. - * Once, the value of dwMaxCCIDMessageLength was 64 and we supported - ICC block chaining, so that we could not handle multple Bulk - transactions. + * Once in the past (version <= 0.4), the value of + dwMaxCCIDMessageLength was 64 and we supported ICC block chaining, + so that we could not handle multple Bulk transactions. * Now, the value of dwMaxCCIDMessageLength is 320, that's the size of header of ICC block plus size of maximum APDU (by 64 - granularity). Still, some ccid implementation sends ICC block - using chaining (unfortunately), so we keep the code of ICC block - chaining. + granularity). Still, some ccid implementation (ccid 1.3.11, for + example) sends ICC block using chaining unfortunately, so we keep + the code of ICC block chaining. OpenPGP card protocol implementation diff --git a/src/openpgp-do.c b/src/openpgp-do.c index 6f9a1b1..e5ef24e 100644 --- a/src/openpgp-do.c +++ b/src/openpgp-do.c @@ -1203,12 +1203,15 @@ gpg_do_get_data (uint16_t tag, int with_tag) if (tag == GPG_DO_CH_CERTIFICATE) { res_APDU_pointer = &ch_certificate_start; - res_APDU_size = (res_APDU_pointer[2] << 8) | res_APDU_pointer[3]; + res_APDU_size = ((res_APDU_pointer[2] << 8) | res_APDU_pointer[3]); if (res_APDU_size == 0xffff) { res_APDU_pointer = NULL; GPG_NO_RECORD (); } + else + /* Add length of (tag+len) and status word (0x9000) at the end */ + res_APDU_size += 4 + 2; } else { diff --git a/src/openpgp.c b/src/openpgp.c index 5760516..a412e8f 100644 --- a/src/openpgp.c +++ b/src/openpgp.c @@ -29,7 +29,6 @@ #include "polarssl/config.h" #include "polarssl/sha1.h" -#define INS_NOP 0x00 #define INS_VERIFY 0x20 #define INS_CHANGE_REFERENCE_DATA 0x24 #define INS_PSO 0x2a @@ -907,6 +906,7 @@ cmd_update_binary (void) return; } + GPG_SUCCESS (); DEBUG_INFO ("UPDATE BINARY done.\r\n"); } @@ -943,8 +943,6 @@ process_command_apdu (void) if (cmds[i].command == cmd) break; - res_APDU_pointer = NULL; /* default */ - if (i < NUM_CMDS) cmds[i].cmd_handler (); else @@ -973,6 +971,8 @@ GPGthread (void *arg) DEBUG_INFO ("GPG!: "); DEBUG_WORD ((uint32_t)&m); + res_APDU_pointer = NULL; /* default */ + if (icc_data_size != 0) { process_command_apdu (); diff --git a/src/usb-icc.c b/src/usb-icc.c index 189e2ec..a4ceeb0 100644 --- a/src/usb-icc.c +++ b/src/usb-icc.c @@ -379,6 +379,7 @@ icc_power_off (void) gpg_thread = NULL; } + icc_state = ICC_STATE_START; /* This status change should be here */ icc_send_status (); DEBUG_INFO ("OFF\r\n"); return ICC_STATE_START; @@ -627,7 +628,7 @@ icc_handle_data (void) else { icc_send_data_block (ICC_RESPONSE_MSG_DATA_SIZE, 0, 0x03); - res_APDU_size -= ICC_MAX_MSG_DATA_SIZE; + res_APDU_size -= ICC_RESPONSE_MSG_DATA_SIZE; } } else @@ -695,30 +696,30 @@ USBthread (void *arg) if (m == EV_RX_DATA_READY) icc_state = icc_handle_data (); else if (m == EV_EXEC_FINISHED) - { - if (icc_state == ICC_STATE_EXECUTE) - { - if (res_APDU_pointer != NULL) + if (icc_state == ICC_STATE_EXECUTE) + { + if (res_APDU_pointer != NULL) + { memcpy (res_APDU, res_APDU_pointer, ICC_RESPONSE_MSG_DATA_SIZE); + 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); - icc_state = ICC_STATE_WAIT; - } - else - { - 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; - } - } - else - { /* XXX: error */ - DEBUG_INFO ("ERR07\r\n"); - } - } + if (res_APDU_size <= ICC_RESPONSE_MSG_DATA_SIZE) + { + icc_send_data_block (res_APDU_size, 0, 0); + icc_state = ICC_STATE_WAIT; + } + else + { + icc_send_data_block (ICC_RESPONSE_MSG_DATA_SIZE, 0, 0x01); + res_APDU_size -= ICC_RESPONSE_MSG_DATA_SIZE; + icc_state = ICC_STATE_SEND; + } + } + else + { /* XXX: error */ + DEBUG_INFO ("ERR07\r\n"); + } else if (m == EV_TX_FINISHED) { if (icc_state == ICC_STATE_START || icc_state == ICC_STATE_WAIT diff --git a/tool/dfuse.py b/tool/dfuse.py index 3b6e52f..2f9c180 100755 --- a/tool/dfuse.py +++ b/tool/dfuse.py @@ -121,7 +121,7 @@ class DFU_STM32: def __del__(self): try: - self.__devhandle.releaseInterface(self.__intf) + self.__devhandle.releaseInterface() del self.__devhandle except: pass diff --git a/tool/dump_mem.py b/tool/dump_mem.py index 88ebd5c..806bafe 100755 --- a/tool/dump_mem.py +++ b/tool/dump_mem.py @@ -1,5 +1,27 @@ #! /usr/bin/python +""" +dump_mem.py - dump memory with DfuSe for STM32 Processor. + +Copyright (C) 2010 Free Software Initiative of Japan +Author: NIIBE Yutaka + +This file is a part of Gnuk, a GnuPG USB Token implementation. + +Gnuk is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Gnuk 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 General Public License +along with this program. If not, see . +""" + import sys from dfuse import * diff --git a/tool/gnuk_update_binary.py b/tool/gnuk_update_binary.py new file mode 100755 index 0000000..a634720 --- /dev/null +++ b/tool/gnuk_update_binary.py @@ -0,0 +1,246 @@ +#! /usr/bin/python + +""" +gnuk_update_binary.py - a tool to put binary to Gnuk Token +This tool is for importing certificate, updating random number, etc. + +Copyright (C) 2011 Free Software Initiative of Japan +Author: NIIBE Yutaka + +This file is a part of Gnuk, a GnuPG USB Token implementation. + +Gnuk is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Gnuk 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 General Public License +along with this program. If not, see . +""" + +from intel_hex import * +from struct import * +import sys, time, struct + +# INPUT: binary file + +# Assume only single CCID device is attached to computer, and it's Gnuk Token + +import usb + +# USB class, subclass, protocol +CCID_CLASS = 0x0B +CCID_SUBCLASS = 0x00 +CCID_PROTOCOL_0 = 0x00 + +def icc_compose(msg_type, data_len, slot, seq, param, data): + return pack('BBBB', cls, ins, p1, p2) + else: + return pack('>BBBBBh', cls, ins, p1, p2, 0, data_len) + data + +# This class only supports Gnuk (for now) +class gnuk_token: + def __init__(self, device, configuration, interface): + """ + __init__(device, configuration, interface) -> None + Initialize the device. + device: usb.Device object. + configuration: configuration number. + interface: usb.Interface object representing the interface and altenate setting. + """ + if interface.interfaceClass != CCID_CLASS: + raise ValueError, "Wrong interface class" + if interface.interfaceSubClass != CCID_SUBCLASS: + raise ValueError, "Wrong interface sub class" + self.__devhandle = device.open() + try: + self.__devhandle.setConfiguration(configuration) + except: + pass + self.__devhandle.claimInterface(interface) + self.__devhandle.setAltInterface(interface) + + self.__intf = interface.interfaceNumber + self.__alt = interface.alternateSetting + self.__conf = configuration + + self.__bulkout = 2 + self.__bulkin = 0x81 + + self.__timeout = 10000 + self.__seq = 0 + + + def __del__(self): + try: + self.__devhandle.releaseInterface() + del self.__devhandle + except: + pass + + def icc_get_result(self): + msg = self.__devhandle.bulkRead(self.__bulkin, 1024, self.__timeout) + if len(msg) < 10: + print msg + raise ValueError, "icc_get_result" + msg_type = msg[0] + data_len = msg[1] + (msg[2]<<8) + (msg[3]<<16) + (msg[4]<<24) + slot = msg[5] + seq = msg[6] + status = msg[7] + error = msg[8] + chain = msg[9] + data = msg[10:] + # XXX: check msg_type, data_len, slot, seq, error + return (status, chain, data) + + def icc_get_status(self): + msg = icc_compose(0x65, 0, 0, self.__seq, 0, "") + self.__devhandle.bulkWrite(self.__bulkout, msg, self.__timeout) + self.__seq += 1 + status, chain, data = self.icc_get_result() + # XXX: check chain, data + return status + + def icc_power_on(self): + msg = icc_compose(0x62, 0, 0, self.__seq, 0, "") + self.__devhandle.bulkWrite(self.__bulkout, msg, self.__timeout) + self.__seq += 1 + status, chain, data = self.icc_get_result() + # XXX: check status, chain + return data # ATR + + def icc_power_off(self): + msg = icc_compose(0x63, 0, 0, self.__seq, 0, "") + self.__devhandle.bulkWrite(self.__bulkout, msg, self.__timeout) + self.__seq += 1 + status, chain, data = self.icc_get_result() + # XXX: check chain, data + return status + + def icc_send_data_block(self, data): + msg = icc_compose(0x6f, len(data), 0, self.__seq, 0, data) + self.__devhandle.bulkWrite(self.__bulkout, msg, self.__timeout) + self.__seq += 1 + return self.icc_get_result() + + def icc_send_cmd(self, data): + status, chain, data_rcv = self.icc_send_data_block(data) + if chain == 0: + return data_rcv + elif chain == 1: + d = data_rcv + while True: + msg = icc_compose(0x6f, 0, 0, self.__seq, 0x10, "") + self.__devhandle.bulkWrite(self.__bulkout, msg, self.__timeout) + self.__seq += 1 + status, chain, data_rcv = self.icc_get_result() + # XXX: check status + d += data_rcv + if chain == 2: + break + elif chain == 3: + continue + else: + raise ValueError, "icc_send_cmd chain" + return d + else: + raise ValueError, "icc_send_cmd" + + def cmd_verify(self, who, passwd): + cmd_data = iso7816_compose(0x20, 0x00, 0x80+who, passwd) + sw = self.icc_send_cmd(cmd_data) + if len(sw) != 2: + raise ValueError, "cmd_verify" + if not (sw[0] == 0x90 and sw[1] == 0x00): + raise ValueError, "cmd_verify" + + def cmd_update_binary(self, fileid, data): + count = 0 + data_len = len(data) + while count*256 < data_len: + if count == 0: + cmd_data = iso7816_compose(0xd6, 0x80+fileid, 0x00, data[:256]) + else: + cmd_data = iso7816_compose(0xd6, count, 0x00, data[256*count:256*(count+1)]) + sw = self.icc_send_cmd(cmd_data) + if len(sw) != 2: + raise ValueError, "cmd_update_binary" + if not (sw[0] == 0x90 and sw[1] == 0x00): + raise ValueError, "cmd_update_binary" + count += 1 + + def cmd_select_openpgp(self): + cmd_data = iso7816_compose(0xa4, 0x04, 0x0c, "\xD2\x76\x00\x01\x24\x01") + sw = self.icc_send_cmd(cmd_data) + if len(sw) != 2: + raise ValueError, "cmd_select_openpgp" + if not (sw[0] == 0x90 and sw[1] == 0x00): + raise ValueError, "cmd_select_openpgp" + + def cmd_get_data(self, tagh, tagl): + cmd_data = iso7816_compose(0xca, tagh, tagl, "") + result = self.icc_send_cmd(cmd_data) + sw = result[-2:] + if not (sw[0] == 0x90 and sw[1] == 0x00): + raise ValueError, "cmd_get_data" + return result[:-2] + +def compare(data_original, data_in_device): + i = 0 + for d in data_original: + if ord(d) != data_in_device[i]: + raise ValueError, "verify failed at %08x" % i + i += 1 + +def get_device(): + busses = usb.busses() + for bus in busses: + devices = bus.devices + for dev in devices: + for config in dev.configurations: + for intf in config.interfaces: + for alt in intf: + if alt.interfaceClass == CCID_CLASS and \ + alt.interfaceSubClass == CCID_SUBCLASS and \ + alt.interfaceProtocol == CCID_PROTOCOL_0: + return dev, config, alt + raise ValueError, "Device not found" + +def main(filename): + f = open(filename) + data = f.read() + f.close() + print "%s: %d" % (filename, len(data)) + data += "\x90\x00" + dev, config, intf = get_device() + print "Device: ", dev.filename + print "Configuration: ", config.value + print "Interface: ", intf.interfaceNumber + icc = gnuk_token(dev, config, intf) + if icc.icc_get_status() == 2: + raise ValueError, "No ICC present" + elif icc.icc_get_status() == 1: + print icc.icc_power_on() + icc.cmd_verify(3, "12345678") + icc.cmd_update_binary(0, data) + icc.cmd_select_openpgp() + data = data[:-2] + data_in_device = icc.cmd_get_data(0x7f, 0x21) + compare(data, data_in_device) + icc.icc_power_off() + return 0 + +if __name__ == '__main__': + main(sys.argv[1])