86de1e4201186841e8886439a89c96799801d985
[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         cmd_data = iso7816_compose(0xc0, 0x00, 0x00, '') + pack('>B', expected_len)
154         response = self.icc_send_cmd(cmd_data)
155         return response[:-2]
156
157     def cmd_verify(self, who, passwd):
158         cmd_data = iso7816_compose(0x20, 0x00, 0x80+who, passwd)
159         sw = self.icc_send_cmd(cmd_data)
160         if len(sw) != 2:
161             raise ValueError, sw
162         if not (sw[0] == 0x90 and sw[1] == 0x00):
163             raise ValueError, sw
164
165     def cmd_read_binary(self, fileid):
166         cmd_data = iso7816_compose(0xb0, 0x80+fileid, 0x00, '')
167         sw = self.icc_send_cmd(cmd_data)
168         if len(sw) != 2:
169             raise ValueError, sw
170         if sw[0] != 0x61:
171             raise ValueError, ("%02x%02x" % (sw[0], sw[1]))
172         return self.cmd_get_response(sw[1])
173
174     def cmd_write_binary(self, fileid, data):
175         count = 0
176         data_len = len(data)
177         while count*256 < data_len:
178             if count == 0:
179                 if len(data) < 128:
180                     cmd_data0 = iso7816_compose(0xd0, 0x80+fileid, 0x00, data[:128])
181                     cmd_data1 = None
182                 else:
183                     cmd_data0 = iso7816_compose(0xd0, 0x80+fileid, 0x00, data[:128], 0x10)
184                     cmd_data1 = iso7816_compose(0xd0, 0x80+fileid, 0x00, data[128:256])
185             else:
186                 if len(data[256*count:256*count+128]) < 128:
187                     cmd_data0 = iso7816_compose(0xd0, count, 0x00, data[256*count:256*count+128])
188                     cmd_data1 = None
189                 else:
190                     cmd_data0 = iso7816_compose(0xd0, count, 0x00, data[256*count:256*count+128], 0x10)
191                     cmd_data1 = iso7816_compose(0xd0, count, 0x00, data[256*count:256*(count+1)])
192             sw = self.icc_send_cmd(cmd_data0)
193             if len(sw) != 2:
194                 raise ValueError, "cmd_write_binary 0"
195             if not (sw[0] == 0x90 and sw[1] == 0x00):
196                 raise ValueError, "cmd_write_binary 0"
197             if cmd_data1:
198                 sw = self.icc_send_cmd(cmd_data1)
199                 if len(sw) != 2:
200                     raise ValueError, "cmd_write_binary 1"
201                 if not (sw[0] == 0x90 and sw[1] == 0x00):
202                     raise ValueError, "cmd_write_binary 1"
203             count += 1
204
205     def cmd_update_binary(self, fileid, data):
206         count = 0
207         data_len = len(data)
208         while count*256 < data_len:
209             if count == 0:
210                 if len(data) < 128:
211                     cmd_data0 = iso7816_compose(0xd6, 0x80+fileid, 0x00, data[:128])
212                     cmd_data1 = None
213                 else:
214                     cmd_data0 = iso7816_compose(0xd6, 0x80+fileid, 0x00, data[:128], 0x10)
215                     cmd_data1 = iso7816_compose(0xd6, 0x80+fileid, 0x00, data[128:256])
216             else:
217                 if len(data[256*count:256*count+128]) < 128:
218                     cmd_data0 = iso7816_compose(0xd6, count, 0x00, data[256*count:256*count+128])
219                     cmd_data1 = None
220                 else:
221                     cmd_data0 = iso7816_compose(0xd6, count, 0x00, data[256*count:256*count+128], 0x10)
222                     cmd_data1 = iso7816_compose(0xd6, count, 0x00, data[256*count:256*(count+1)])
223             sw = self.icc_send_cmd(cmd_data0)
224             if len(sw) != 2:
225                 raise ValueError, "cmd_write_binary 0"
226             if not (sw[0] == 0x90 and sw[1] == 0x00):
227                 raise ValueError, "cmd_write_binary 0"
228             if cmd_data1:
229                 sw = self.icc_send_cmd(cmd_data1)
230                 if len(sw) != 2:
231                     raise ValueError, "cmd_write_binary 1"
232                 if not (sw[0] == 0x90 and sw[1] == 0x00):
233                     raise ValueError, "cmd_write_binary 1"
234             count += 1
235
236     def cmd_select_openpgp(self):
237         cmd_data = iso7816_compose(0xa4, 0x04, 0x0c, "\xD2\x76\x00\x01\x24\x01")
238         sw = self.icc_send_cmd(cmd_data)
239         if len(sw) != 2:
240             raise ValueError, sw
241         if not (sw[0] == 0x90 and sw[1] == 0x00):
242             raise ValueError, ("%02x%02x" % (sw[0], sw[1]))
243
244     def cmd_get_data(self, tagh, tagl):
245         cmd_data = iso7816_compose(0xca, tagh, tagl, "")
246         sw = self.icc_send_cmd(cmd_data)
247         if len(sw) != 2:
248             raise ValueError, sw
249         if sw[0] != 0x61:
250             raise ValueError, ("%02x%02x" % (sw[0], sw[1]))
251         return self.cmd_get_response(sw[1])
252
253 def compare(data_original, data_in_device):
254     i = 0 
255     for d in data_original:
256         if ord(d) != data_in_device[i]:
257             raise ValueError, "verify failed at %08x" % i
258         i += 1
259
260 def gnuk_devices():
261     busses = usb.busses()
262     for bus in busses:
263         devices = bus.devices
264         for dev in devices:
265             for config in dev.configurations:
266                 for intf in config.interfaces:
267                     for alt in intf:
268                         if alt.interfaceClass == CCID_CLASS and \
269                                 alt.interfaceSubClass == CCID_SUBCLASS and \
270                                 alt.interfaceProtocol == CCID_PROTOCOL_0:
271                             yield dev, config, alt
272
273 DEFAULT_PW3 = "12345678"
274 BY_ADMIN = 3
275
276 def main(fileid, is_update, data, passwd):
277     icc = None
278     for (dev, config, intf) in gnuk_devices():
279         try:
280             icc = gnuk_token(dev, config, intf)
281             print "Device: ", dev.filename
282             print "Configuration: ", config.value
283             print "Interface: ", intf.interfaceNumber
284             break
285         except:
286             pass
287     if icc.icc_get_status() == 2:
288         raise ValueError, "No ICC present"
289     elif icc.icc_get_status() == 1:
290         icc.icc_power_on()
291     icc.cmd_verify(BY_ADMIN, passwd)
292     if is_update:
293         icc.cmd_update_binary(fileid, data)
294     else:
295         icc.cmd_write_binary(fileid, data)
296     icc.cmd_select_openpgp()
297     if fileid == 0:
298         data_in_device = icc.cmd_get_data(0x00, 0x4f)
299         for d in data_in_device:
300             print "%02x" % d,
301         print
302         compare(data, data_in_device[8:])
303     elif fileid >= 1 and fileid <= 4:
304         data_in_device = icc.cmd_read_binary(fileid)
305         compare(data, data_in_device)
306     else:
307         data_in_device = icc.cmd_get_data(0x7f, 0x21)
308         compare(data, data_in_device)
309     icc.icc_power_off()
310     return 0
311
312 if __name__ == '__main__':
313     passwd = DEFAULT_PW3
314     if sys.argv[1] == '-p':
315         from getpass import getpass
316         passwd = getpass("Admin password: ")
317         sys.argv.pop(1)
318     if sys.argv[1] == '-u':
319         is_update = True
320         sys.argv.pop(1)
321     else:
322         is_update = False
323     if sys.argv[1] == '-s':
324         fileid = 0              # serial number
325         filename = sys.argv[2]
326         f = open(filename)
327         email = os.environ['EMAIL']
328         serial_data_hex = None
329         for line in f.readlines():
330             field = string.split(line)
331             if field[0] == email:
332                 serial_data_hex = field[1].replace(':','')
333         f.close()
334         if not serial_data_hex:
335             print "No serial number"
336             exit(1)
337         print "Writing serial number"
338         data = binascii.unhexlify(serial_data_hex)
339     elif sys.argv[1] == '-k':   # firmware update key
340         keyno = sys.argv[2]
341         fileid = 1 + int(keyno)
342         filename = sys.argv[3]
343         f = open(filename)
344         data = f.read()
345         f.close()
346     else:
347         fileid = 5              # Card holder certificate
348         filename = sys.argv[1]
349         f = open(filename)
350         data = f.read()
351         f.close()
352         print "%s: %d" % (filename, len(data))
353         print "Updating card holder certificate"
354     main(fileid, is_update, data, passwd)