ST-Link/V2 flash ROM writer
[gnuk/gnuk.git] / tool / gnuk_put_binary.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, writing serial 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 import sys, os, binascii, string
27
28 # INPUT: binary file
29
30 # Assume only single CCID device is attached to computer and it's Gnuk Token
31
32 from smartcard.CardType import AnyCardType
33 from smartcard.CardRequest import CardRequest
34 from smartcard.util import toHexString
35
36 def s2l(s):
37     return [ ord(c) for c in s ]
38
39 class GnukToken(object):
40     def __init__(self):
41         cardtype = AnyCardType()
42         cardrequest = CardRequest(timeout=1, cardType=cardtype)
43         cardservice = cardrequest.waitforcard()
44         self.connection = cardservice.connection
45
46     def cmd_get_response(self, expected_len):
47         result = []
48         while True:
49             apdu = [0x00, 0xc0, 0x00, 0x00, expected_len]
50             response, sw1, sw2 = self.connection.transmit(apdu)
51             result += response
52             if sw1 == 0x90 and sw2 == 0x00:
53                 return result
54             elif sw1 != 0x61: 
55                 raise ValueError, ("%02x%02x" % (sw1, sw2))
56             else:
57                 expected_len = sw2
58
59     def cmd_verify(self, who, passwd):
60         apdu = [0x00, 0x20, 0x00, 0x80+who, len(passwd)] + s2l(passwd)
61         response, sw1, sw2 = self.connection.transmit(apdu)
62         if not (sw1 == 0x90 and sw2 == 0x00):
63             raise ValueError, ("%02x%02x" % (sw1, sw2))
64
65     def cmd_read_binary(self, fileid):
66         apdu = [0x00, 0xb0, 0x80+fileid, 0x00] 
67         response, sw1, sw2 = self.connection.transmit(apdu)
68         if sw1 == 0x61:
69             response = self.cmd_get_response(sw2)
70         elif not (sw1 == 0x90 and sw2 == 0x00):
71             raise ValueError, ("%02x%02x" % (sw1, sw2))
72         return response
73
74     def cmd_write_binary(self, fileid, data, is_update):
75         count = 0
76         data_len = len(data)
77         if is_update:
78             ins = 0xd6
79         else:
80             ins = 0xd0
81         while count*256 < data_len:
82             if count == 0:
83                 d = data[:256]
84                 if len(d) <= 255:
85                     apdu = [0x00, ins, 0x80+fileid, 0x00, len(d)] + s2l(d)
86                 else:
87                     apdu0 = [0x10, ins, 0x80+fileid, 0x00, 255] + s2l(d[:255])
88                     response, sw1, sw2 = self.connection.transmit(apdu0)
89                     apdu = [0x00, ins, 0x80+fileid, 0x00, 1 ] + s2l(d[255:])
90             else:
91                 d = data[256*count:256*(count+1)]
92                 if len(d) <= 255:
93                     apdu = [0x00, ins, count, 0x00, len(d)] + s2l(d)
94                 else:
95                     apdu0 = [0x10, ins, count, 0x00, 255] + s2l(d[:255])
96                     response, sw1, sw2 = self.connection.transmit(apdu0)
97                     apdu = [0x00, ins, count, 0x00, 1] + s2l(d[255:])
98             response, sw1, sw2 = self.connection.transmit(apdu)
99             if not (sw1 == 0x90 and sw2 == 0x00):
100                 if is_update:
101                     raise ValueError, ("update failure: %02x%02x" % (sw1, sw2))
102                 else:
103                     raise ValueError, ("write failure: %02x%02x" % (sw1, sw2))
104             count += 1
105
106     def cmd_select_openpgp(self):
107         apdu = [0x00, 0xa4, 0x04, 0x0c, 6, 0xd2, 0x76, 0x00, 0x01, 0x24, 0x01]
108         response, sw1, sw2 = self.connection.transmit(apdu)
109         if sw1 == 0x61:
110             response = self.cmd_get_response(sw2)
111         elif not (sw1 == 0x90 and sw2 == 0x00):
112             raise ValueError, ("%02x%02x" % (sw1, sw2))
113
114     def cmd_get_data(self, tagh, tagl):
115         apdu = [0x00, 0xca, tagh, tagl]
116         response, sw1, sw2 = self.connection.transmit(apdu)
117         if sw1 == 0x61:
118             response = self.cmd_get_response(sw2)
119         elif not (sw1 == 0x90 and sw2 == 0x00):
120             raise ValueError, ("%02x%02x" % (sw1, sw2))
121         return response
122
123 def compare(data_original, data_in_device):
124     i = 0 
125     for d in data_original:
126         if ord(d) != data_in_device[i]:
127             raise ValueError, "verify failed at %08x" % i
128         i += 1
129
130 DEFAULT_PW3 = "12345678"
131 BY_ADMIN = 3
132
133 def main(fileid, is_update, data, passwd):
134     gnuk = GnukToken()
135
136     gnuk.connection.connect()
137     print "Token:", gnuk.connection.getReader()
138     print "ATR:", toHexString( gnuk.connection.getATR() )
139
140     gnuk.cmd_verify(BY_ADMIN, passwd)
141     gnuk.cmd_write_binary(fileid, data, is_update)
142     gnuk.cmd_select_openpgp()
143     if fileid == 0:
144         data_in_device = gnuk.cmd_get_data(0x00, 0x4f)
145         for d in data_in_device:
146             print "%02x" % d,
147         print
148         compare(data, data_in_device[8:])
149     elif fileid >= 1 and fileid <= 4:
150         data_in_device = gnuk.cmd_read_binary(fileid)
151         compare(data, data_in_device)
152     elif fileid == 5:
153         data_in_device = gnuk.cmd_get_data(0x7f, 0x21)
154         compare(data, data_in_device)
155
156     gnuk.connection.disconnect()
157     return 0
158
159
160 if __name__ == '__main__':
161     passwd = DEFAULT_PW3
162     if sys.argv[1] == '-p':
163         from getpass import getpass
164         passwd = getpass("Admin password: ")
165         sys.argv.pop(1)
166     if sys.argv[1] == '-u':
167         is_update = True
168         sys.argv.pop(1)
169     else:
170         is_update = False
171     if sys.argv[1] == '-s':
172         fileid = 0              # serial number
173         filename = sys.argv[2]
174         f = open(filename)
175         email = os.environ['EMAIL']
176         serial_data_hex = None
177         for line in f.readlines():
178             field = string.split(line)
179             if field[0] == email:
180                 serial_data_hex = field[1].replace(':','')
181         f.close()
182         if not serial_data_hex:
183             print "No serial number"
184             exit(1)
185         print "Writing serial number"
186         data = binascii.unhexlify(serial_data_hex)
187     elif sys.argv[1] == '-k':   # firmware update key
188         keyno = sys.argv[2]
189         fileid = 1 + int(keyno)
190         filename = sys.argv[3]
191         f = open(filename)
192         data = f.read()
193         f.close()
194     else:
195         fileid = 5              # Card holder certificate
196         filename = sys.argv[1]
197         f = open(filename)
198         data = f.read()
199         f.close()
200         print "%s: %d" % (filename, len(data))
201         print "Updating card holder certificate"
202     main(fileid, is_update, data, passwd)