CertDO bug fixes
[gnuk/gnuk.git] / tool / gnuk_put_binary_libusb.py
1 #! /usr/bin/python
2
3 """
4 gnuk_put_binary.py - a tool to put binary to Gnuk Token
5 This tool is for importing certificate, updating random number, etc.
6
7 Copyright (C) 2011, 2012 Free Software Initiative of Japan
8 Author: NIIBE Yutaka <gniibe@fsij.org>
9
10 This file is a part of Gnuk, a GnuPG USB Token implementation.
11
12 Gnuk is free software: you can redistribute it and/or modify it
13 under the terms of the GNU General Public License as published by
14 the Free Software Foundation, either version 3 of the License, or
15 (at your option) any later version.
16
17 Gnuk is distributed in the hope that it will be useful, but WITHOUT
18 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
19 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
20 License for more details.
21
22 You should have received a copy of the GNU General Public License
23 along with this program.  If not, see <http://www.gnu.org/licenses/>.
24 """
25
26 from intel_hex import *
27 from struct import *
28 import sys, time, os, binascii, string
29
30 # INPUT: binary file
31
32 # Assume only single CCID device is attached to computer, and it's Gnuk Token
33
34 import usb
35
36 # USB class, subclass, protocol
37 CCID_CLASS = 0x0B
38 CCID_SUBCLASS = 0x00
39 CCID_PROTOCOL_0 = 0x00
40
41 def icc_compose(msg_type, data_len, slot, seq, param, data):
42     return pack('<BiBBBH', msg_type, data_len, slot, seq, 0, param) + data
43
44 def iso7816_compose(ins, p1, p2, data, cls=0x00):
45     data_len = len(data)
46     if data_len == 0:
47         return pack('>BBBB', cls, ins, p1, p2)
48     else:
49         return pack('>BBBBB', cls, ins, p1, p2, data_len) + data
50
51 # This class only supports Gnuk (for now) 
52 class gnuk_token:
53     def __init__(self, device, configuration, interface):
54         """
55         __init__(device, configuration, interface) -> None
56         Initialize the device.
57         device: usb.Device object.
58         configuration: configuration number.
59         interface: usb.Interface object representing the interface and altenate setting.
60         """
61         if interface.interfaceClass != CCID_CLASS:
62             raise ValueError, "Wrong interface class"
63         if interface.interfaceSubClass != CCID_SUBCLASS:
64             raise ValueError, "Wrong interface sub class"
65         self.__devhandle = device.open()
66         try:
67             self.__devhandle.setConfiguration(configuration)
68         except:
69             pass
70         self.__devhandle.claimInterface(interface)
71         self.__devhandle.setAltInterface(interface)
72
73         self.__intf = interface.interfaceNumber
74         self.__alt = interface.alternateSetting
75         self.__conf = configuration
76
77         self.__bulkout = 1
78         self.__bulkin  = 0x81
79
80         self.__timeout = 10000
81         self.__seq = 0
82
83     def icc_get_result(self):
84         msg = self.__devhandle.bulkRead(self.__bulkin, 1024, self.__timeout)
85         if len(msg) < 10:
86             print msg
87             raise ValueError, "icc_get_result"
88         msg_type = msg[0]
89         data_len = msg[1] + (msg[2]<<8) + (msg[3]<<16) + (msg[4]<<24)
90         slot = msg[5]
91         seq = msg[6]
92         status = msg[7]
93         error = msg[8]
94         chain = msg[9]
95         data = msg[10:]
96         # XXX: check msg_type, data_len, slot, seq, error
97         return (status, chain, data)
98
99     def icc_get_status(self):
100         msg = icc_compose(0x65, 0, 0, self.__seq, 0, "")
101         self.__devhandle.bulkWrite(self.__bulkout, msg, self.__timeout)
102         self.__seq += 1
103         status, chain, data = self.icc_get_result()
104         # XXX: check chain, data
105         return status
106
107     def icc_power_on(self):
108         msg = icc_compose(0x62, 0, 0, self.__seq, 0, "")
109         self.__devhandle.bulkWrite(self.__bulkout, msg, self.__timeout)
110         self.__seq += 1
111         status, chain, data = self.icc_get_result()
112         # XXX: check status, chain
113         return data             # ATR
114
115     def icc_power_off(self):
116         msg = icc_compose(0x63, 0, 0, self.__seq, 0, "")
117         self.__devhandle.bulkWrite(self.__bulkout, msg, self.__timeout)
118         self.__seq += 1
119         status, chain, data = self.icc_get_result()
120         # XXX: check chain, data
121         return status
122
123     def icc_send_data_block(self, data):
124         msg = icc_compose(0x6f, len(data), 0, self.__seq, 0, data)
125         self.__devhandle.bulkWrite(self.__bulkout, msg, self.__timeout)
126         self.__seq += 1
127         return self.icc_get_result()
128
129     def icc_send_cmd(self, data):
130         status, chain, data_rcv = self.icc_send_data_block(data)
131         if chain == 0:
132             return data_rcv
133         elif chain == 1:
134             d = data_rcv
135             while True:
136                 msg = icc_compose(0x6f, 0, 0, self.__seq, 0x10, "")
137                 self.__devhandle.bulkWrite(self.__bulkout, msg, self.__timeout)
138                 self.__seq += 1
139                 status, chain, data_rcv = self.icc_get_result()
140                 # XXX: check status
141                 d += data_rcv
142                 if chain == 2:
143                     break
144                 elif chain == 3:
145                     continue
146                 else:
147                     raise ValueError, "icc_send_cmd chain"
148             return d
149         else:
150             raise ValueError, "icc_send_cmd"
151
152     def cmd_get_response(self, expected_len):
153         result = []
154         while True:
155             cmd_data = iso7816_compose(0xc0, 0x00, 0x00, '') + pack('>B', expected_len)
156             response = self.icc_send_cmd(cmd_data)
157             result += response[:-2]
158             sw = response[-2:]
159             if sw[0] == 0x90 and sw[1] == 0x00:
160                 return result
161             elif sw[0] != 0x61:
162                 raise ValueError, ("%02x%02x" % (sw[0], sw[1]))
163             else:
164                 expected_len = sw[1]
165
166     def cmd_verify(self, who, passwd):
167         cmd_data = iso7816_compose(0x20, 0x00, 0x80+who, passwd)
168         sw = self.icc_send_cmd(cmd_data)
169         if len(sw) != 2:
170             raise ValueError, sw
171         if not (sw[0] == 0x90 and sw[1] == 0x00):
172             raise ValueError, sw
173
174     def cmd_read_binary(self, fileid):
175         cmd_data = iso7816_compose(0xb0, 0x80+fileid, 0x00, '')
176         sw = self.icc_send_cmd(cmd_data)
177         if len(sw) != 2:
178             raise ValueError, sw
179         if sw[0] != 0x61:
180             raise ValueError, ("%02x%02x" % (sw[0], sw[1]))
181         return self.cmd_get_response(sw[1])
182
183     def cmd_write_binary(self, fileid, data):
184         count = 0
185         data_len = len(data)
186         while count*256 < data_len:
187             if count == 0:
188                 if len(data) < 128:
189                     cmd_data0 = iso7816_compose(0xd0, 0x80+fileid, 0x00, data[:128])
190                     cmd_data1 = None
191                 else:
192                     cmd_data0 = iso7816_compose(0xd0, 0x80+fileid, 0x00, data[:128], 0x10)
193                     cmd_data1 = iso7816_compose(0xd0, 0x80+fileid, 0x00, data[128:256])
194             else:
195                 if len(data[256*count:256*count+128]) < 128:
196                     cmd_data0 = iso7816_compose(0xd0, count, 0x00, data[256*count:256*count+128])
197                     cmd_data1 = None
198                 else:
199                     cmd_data0 = iso7816_compose(0xd0, count, 0x00, data[256*count:256*count+128], 0x10)
200                     cmd_data1 = iso7816_compose(0xd0, count, 0x00, data[256*count+128:256*(count+1)])
201             sw = self.icc_send_cmd(cmd_data0)
202             if len(sw) != 2:
203                 raise ValueError, "cmd_write_binary 0"
204             if not (sw[0] == 0x90 and sw[1] == 0x00):
205                 raise ValueError, "cmd_write_binary 0"
206             if cmd_data1:
207                 sw = self.icc_send_cmd(cmd_data1)
208                 if len(sw) != 2:
209                     raise ValueError, "cmd_write_binary 1"
210                 if not (sw[0] == 0x90 and sw[1] == 0x00):
211                     raise ValueError, "cmd_write_binary 1"
212             count += 1
213
214     def cmd_update_binary(self, fileid, data):
215         count = 0
216         data_len = len(data)
217         while count*256 < data_len:
218             if count == 0:
219                 if len(data) < 128:
220                     cmd_data0 = iso7816_compose(0xd6, 0x80+fileid, 0x00, data[:128])
221                     cmd_data1 = None
222                 else:
223                     cmd_data0 = iso7816_compose(0xd6, 0x80+fileid, 0x00, data[:128], 0x10)
224                     cmd_data1 = iso7816_compose(0xd6, 0x80+fileid, 0x00, data[128:256])
225             else:
226                 if len(data[256*count:256*count+128]) < 128:
227                     cmd_data0 = iso7816_compose(0xd6, count, 0x00, data[256*count:256*count+128])
228                     cmd_data1 = None
229                 else:
230                     cmd_data0 = iso7816_compose(0xd6, count, 0x00, data[256*count:256*count+128], 0x10)
231                     cmd_data1 = iso7816_compose(0xd6, count, 0x00, data[256*count+128:256*(count+1)])
232             sw = self.icc_send_cmd(cmd_data0)
233             if len(sw) != 2:
234                 raise ValueError, "cmd_update_binary 0"
235             if not (sw[0] == 0x90 and sw[1] == 0x00):
236                 raise ValueError, "cmd_update_binary 0"
237             if cmd_data1:
238                 sw = self.icc_send_cmd(cmd_data1)
239                 if len(sw) != 2:
240                     raise ValueError, "cmd_update_binary 1"
241                 if not (sw[0] == 0x90 and sw[1] == 0x00):
242                     raise ValueError, "cmd_update_binary 1"
243             count += 1
244
245     def cmd_select_openpgp(self):
246         cmd_data = iso7816_compose(0xa4, 0x04, 0x0c, "\xD2\x76\x00\x01\x24\x01")
247         sw = self.icc_send_cmd(cmd_data)
248         if len(sw) != 2:
249             raise ValueError, sw
250         if not (sw[0] == 0x90 and sw[1] == 0x00):
251             raise ValueError, ("%02x%02x" % (sw[0], sw[1]))
252
253     def cmd_get_data(self, tagh, tagl):
254         cmd_data = iso7816_compose(0xca, tagh, tagl, "")
255         sw = self.icc_send_cmd(cmd_data)
256         if len(sw) != 2:
257             raise ValueError, sw
258         if sw[0] != 0x61:
259             raise ValueError, ("%02x%02x" % (sw[0], sw[1]))
260         return self.cmd_get_response(sw[1])
261
262 def compare(data_original, data_in_device):
263     i = 0 
264     for d in data_original:
265         if ord(d) != data_in_device[i]:
266             raise ValueError, "verify failed at %08x" % i
267         i += 1
268
269 def gnuk_devices():
270     busses = usb.busses()
271     for bus in busses:
272         devices = bus.devices
273         for dev in devices:
274             for config in dev.configurations:
275                 for intf in config.interfaces:
276                     for alt in intf:
277                         if alt.interfaceClass == CCID_CLASS and \
278                                 alt.interfaceSubClass == CCID_SUBCLASS and \
279                                 alt.interfaceProtocol == CCID_PROTOCOL_0:
280                             yield dev, config, alt
281
282 DEFAULT_PW3 = "12345678"
283 BY_ADMIN = 3
284
285 def main(fileid, is_update, data, passwd):
286     icc = None
287     for (dev, config, intf) in gnuk_devices():
288         try:
289             icc = gnuk_token(dev, config, intf)
290             print "Device: ", dev.filename
291             print "Configuration: ", config.value
292             print "Interface: ", intf.interfaceNumber
293             break
294         except:
295             pass
296     if icc.icc_get_status() == 2:
297         raise ValueError, "No ICC present"
298     elif icc.icc_get_status() == 1:
299         icc.icc_power_on()
300     icc.cmd_verify(BY_ADMIN, passwd)
301     if is_update:
302         icc.cmd_update_binary(fileid, data)
303     else:
304         icc.cmd_write_binary(fileid, data)
305     icc.cmd_select_openpgp()
306     if fileid == 0:
307         data_in_device = icc.cmd_get_data(0x00, 0x4f)
308         for d in data_in_device:
309             print "%02x" % d,
310         print
311         compare(data, data_in_device[8:])
312     elif fileid >= 1 and fileid <= 4:
313         data_in_device = icc.cmd_read_binary(fileid)
314         compare(data, data_in_device)
315     else:
316         data_in_device = icc.cmd_get_data(0x7f, 0x21)
317         compare(data, data_in_device)
318     icc.icc_power_off()
319     return 0
320
321 if __name__ == '__main__':
322     passwd = DEFAULT_PW3
323     if sys.argv[1] == '-p':
324         from getpass import getpass
325         passwd = getpass("Admin password: ")
326         sys.argv.pop(1)
327     if sys.argv[1] == '-u':
328         is_update = True
329         sys.argv.pop(1)
330     else:
331         is_update = False
332     if sys.argv[1] == '-s':
333         fileid = 0              # serial number
334         filename = sys.argv[2]
335         f = open(filename)
336         email = os.environ['EMAIL']
337         serial_data_hex = None
338         for line in f.readlines():
339             field = string.split(line)
340             if field[0] == email:
341                 serial_data_hex = field[1].replace(':','')
342         f.close()
343         if not serial_data_hex:
344             print "No serial number"
345             exit(1)
346         print "Writing serial number"
347         data = binascii.unhexlify(serial_data_hex)
348     elif sys.argv[1] == '-k':   # firmware update key
349         keyno = sys.argv[2]
350         fileid = 1 + int(keyno)
351         filename = sys.argv[3]
352         f = open(filename)
353         data = f.read()
354         f.close()
355     else:
356         fileid = 5              # Card holder certificate
357         filename = sys.argv[1]
358         f = open(filename)
359         data = f.read()
360         f.close()
361         print "%s: %d" % (filename, len(data))
362         print "Updating card holder certificate"
363     main(fileid, is_update, data, passwd)