f3bd953fc43e12a705c482492e15d17c92fe71d4
[gnuk/gnuk.git] / tests / openpgp_card.py
1 """
2 openpgp_card.py - a library for OpenPGP card
3
4 Copyright (C) 2011, 2012, 2013, 2015, 2016, 2018, 2019
5               Free Software Initiative of Japan
6 Author: NIIBE Yutaka <gniibe@fsij.org>
7
8 This file is a part of Gnuk, a GnuPG USB Token implementation.
9
10 Gnuk is free software: you can redistribute it and/or modify it
11 under the terms of the GNU General Public License as published by
12 the Free Software Foundation, either version 3 of the License, or
13 (at your option) any later version.
14
15 Gnuk is distributed in the hope that it will be useful, but WITHOUT
16 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
17 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
18 License for more details.
19
20 You should have received a copy of the GNU General Public License
21 along with this program.  If not, see <http://www.gnu.org/licenses/>.
22 """
23
24 from struct import pack, unpack
25 from kdf_calc import kdf_calc
26
27 def iso7816_compose(ins, p1, p2, data, cls=0x00, le=None):
28     data_len = len(data)
29     if data_len == 0:
30         if not le:
31             return pack('>BBBB', cls, ins, p1, p2)
32         else:
33             return pack('>BBBBB', cls, ins, p1, p2, le)
34     else:
35         if not le:
36             if data_len <= 255:
37                 return pack('>BBBBB', cls, ins, p1, p2, data_len) + data
38             else:
39                 return pack('>BBBBBH', cls, ins, p1, p2, 0, data_len) \
40                     + data
41         else:
42             if data_len <= 255 and le < 256:
43                 return pack('>BBBBB', cls, ins, p1, p2, data_len) \
44                     + data + pack('>B', le)
45             else:
46                 return pack('>BBBBBH', cls, ins, p1, p2, 0, data_len) \
47                     + data + pack('>H', le)
48
49 class OpenPGP_Card(object):
50     def __init__(self, reader):
51         """
52         __init__(reader) -> None
53         Initialize a OpenPGP card with a CardReader.
54         reader: CardReader object.
55         """
56
57         self.__reader = reader
58         self.__kdf_iters = None
59         self.__kdf_salt_user = None
60         self.__kdf_salt_reset = None
61         self.__kdf_salt_admin = None
62         self.is_gnuk = (reader.get_string(2) == "Gnuk Token")
63
64     def configure_with_kdf(self):
65         kdf_data = self.cmd_get_data(0x00, 0xf9)
66         if kdf_data != b"":
67             algo, subalgo, iters, salt_user, salt_reset, salt_admin, hash_user, hash_admin = parse_kdf_data(kdf_data)
68             self.__kdf_iters = iters
69             self.__kdf_salt_user = salt_user
70             self.__kdf_salt_reset = salt_reset
71             self.__kdf_salt_admin = salt_admin
72         else:
73             self.__kdf_iters = None
74             self.__kdf_salt_user = None
75             self.__kdf_salt_reset = None
76             self.__kdf_salt_admin = None
77
78     # Higher layer VERIFY possibly using KDF Data Object
79     def verify(self, who, passwd):
80         if self.__kdf_iters:
81             salt = self.__kdf_salt_user
82             if who == 3 and self.__kdf_salt_admin:
83                     salt = self.__kdf_salt_admin
84             pw_hash = kdf_calc(passwd, salt, self.__kdf_iters)
85             return self.cmd_verify(who, pw_hash)
86         else:
87             return self.cmd_verify(who, passwd)
88
89     # Higher layer CHANGE_PASSWD possibly using KDF Data Object
90     def change_passwd(self, who, passwd_old, passwd_new):
91         if self.__kdf_iters:
92             salt = self.__kdf_salt_user
93             if who == 3 and self.__kdf_salt_admin:
94                     salt = self.__kdf_salt_admin
95             hash_old = kdf_calc(passwd_old, salt, self.__kdf_iters)
96             if passwd_new:
97                 hash_new = kdf_calc(passwd_new, salt, self.__kdf_iters)
98             else:
99                 hash_new = b""
100             return self.cmd_change_reference_data(who, hash_old + hash_new)
101         else:
102             if not passwd_new:
103                 passwd_new = b""
104             return self.cmd_change_reference_data(who, passwd_old + passwd_new)
105
106     # Higher layer SETUP_RESET_CODE possibly using KDF Data Object
107     def setup_reset_code(self, resetcode):
108         if self.__kdf_iters:
109             salt = self.__kdf_salt_user
110             if self.__kdf_salt_reset:
111                     salt = self.__kdf_salt_user
112             reset_hash = kdf_calc(resetcode, salt, self.__kdf_iters)
113             return self.cmd_put_data(0x00, 0xd3, reset_hash)
114         else:
115             return self.cmd_put_data(0x00, 0xd3, resetcode)
116
117     # Higher layer reset passwd possibly using KDF Data Object
118     def reset_passwd_by_resetcode(self, resetcode, pw1):
119         if self.__kdf_iters:
120             salt = self.__kdf_salt_user
121             if self.__kdf_salt_reset:
122                     salt = self.__kdf_salt_user
123             reset_hash = kdf_calc(resetcode, salt, self.__kdf_iters)
124             pw1_hash = kdf_calc(pw1, self.__kdf_salt_user, self.__kdf_iters)
125             return self.cmd_reset_retry_counter(0, 0x81, reset_hash + pw1_hash)
126         else:
127             return self.cmd_reset_retry_counter(0, 0x81, resetcode + pw1)
128
129     # Higher layer reset passwd possibly using KDF Data Object
130     def reset_passwd_by_admin(self, pw1):
131         if self.__kdf_iters:
132             pw1_hash = kdf_calc(pw1, self.__kdf_salt_user, self.__kdf_iters)
133             return self.cmd_reset_retry_counter(2, 0x81, pw1_hash)
134         else:
135             return self.cmd_reset_retry_counter(2, 0x81, pw1)
136
137     def cmd_get_response(self, expected_len):
138         result = b""
139         while True:
140             cmd_data = iso7816_compose(0xc0, 0x00, 0x00, b'') + pack('>B', expected_len)
141             response = self.__reader.send_cmd(cmd_data)
142             result += response[:-2]
143             sw = response[-2:]
144             if sw[0] == 0x90 and sw[1] == 0x00:
145                 return result
146             elif sw[0] != 0x61:
147                 raise ValueError("%02x%02x" % (sw[0], sw[1]))
148             else:
149                 expected_len = sw[1]
150
151     def cmd_verify(self, who, passwd):
152         cmd_data = iso7816_compose(0x20, 0x00, 0x80+who, passwd)
153         sw = self.__reader.send_cmd(cmd_data)
154         if len(sw) != 2:
155             raise ValueError(sw)
156         if not (sw[0] == 0x90 and sw[1] == 0x00):
157             raise ValueError("%02x%02x" % (sw[0], sw[1]))
158         return True
159
160     def cmd_read_binary(self, fileid):
161         cmd_data = iso7816_compose(0xb0, 0x80+fileid, 0x00, b'')
162         sw = self.__reader.send_cmd(cmd_data)
163         if len(sw) != 2:
164             raise ValueError(sw)
165         if sw[0] != 0x61:
166             raise ValueError("%02x%02x" % (sw[0], sw[1]))
167         return self.cmd_get_response(sw[1])
168
169     def cmd_write_binary(self, fileid, data, is_update):
170         count = 0
171         data_len = len(data)
172         if is_update:
173             ins = 0xd6
174         else:
175             ins = 0xd0
176         while count*256 < data_len:
177             if count == 0:
178                 if len(data) < 128:
179                     cmd_data0 = iso7816_compose(ins, 0x80+fileid, 0x00, data[:128])
180                     cmd_data1 = None
181                 else:
182                     cmd_data0 = iso7816_compose(ins, 0x80+fileid, 0x00, data[:128], 0x10)
183                     cmd_data1 = iso7816_compose(ins, 0x80+fileid, 0x00, data[128:256])
184             else:
185                 if len(data[256*count:256*count+128]) < 128:
186                     cmd_data0 = iso7816_compose(ins, count, 0x00, data[256*count:256*count+128])
187                     cmd_data1 = None
188                 else:
189                     cmd_data0 = iso7816_compose(ins, count, 0x00, data[256*count:256*count+128], 0x10)
190                     cmd_data1 = iso7816_compose(ins, count, 0x00, data[256*count+128:256*(count+1)])
191             sw = self.__reader.send_cmd(cmd_data0)
192             if len(sw) != 2:
193                 raise ValueError("cmd_write_binary 0")
194             if not (sw[0] == 0x90 and sw[1] == 0x00):
195                 raise ValueError("cmd_write_binary 0", "%02x%02x" % (sw[0], sw[1]))
196             if cmd_data1:
197                 sw = self.__reader.send_cmd(cmd_data1)
198                 if len(sw) != 2:
199                     raise ValueError("cmd_write_binary 1", sw)
200                 if not (sw[0] == 0x90 and sw[1] == 0x00):
201                     raise ValueError("cmd_write_binary 1", "%02x%02x" % (sw[0], sw[1]))
202             count += 1
203
204     def cmd_select_openpgp(self):
205         cmd_data = iso7816_compose(0xa4, 0x04, 0x00, b"\xD2\x76\x00\x01\x24\x01")
206         r = self.__reader.send_cmd(cmd_data)
207         if len(r) < 2:
208             raise ValueError(r)
209         sw = r[-2:]
210         r = r[0:-2]
211         if sw[0] == 0x61:
212             self.cmd_get_response(sw[1])
213             return True
214         if not (sw[0] == 0x90 and sw[1] == 0x00):
215             raise ValueError("%02x%02x" % (sw[0], sw[1]))
216         return True
217
218     def cmd_get_data(self, tagh, tagl):
219         cmd_data = iso7816_compose(0xca, tagh, tagl, b"", le=254)
220         sw = self.__reader.send_cmd(cmd_data)
221         if len(sw) < 2:
222             raise ValueError(sw)
223         if sw[0] == 0x61:
224             return self.cmd_get_response(sw[1])
225         elif sw[-2] == 0x90 and sw[-1] == 0x00:
226             return sw[0:-2]
227         if sw[0] == 0x6a and sw[1] == 0x88:
228             return None
229         else:
230             raise ValueError("%02x%02x" % (sw[0], sw[1]))
231
232     def cmd_change_reference_data(self, who, data):
233         cmd_data = iso7816_compose(0x24, 0x00, 0x80+who, data)
234         sw = self.__reader.send_cmd(cmd_data)
235         if len(sw) != 2:
236             raise ValueError(sw)
237         if not (sw[0] == 0x90 and sw[1] == 0x00):
238             raise ValueError("%02x%02x" % (sw[0], sw[1]))
239         return True
240
241     def cmd_put_data(self, tagh, tagl, content):
242         cmd_data = iso7816_compose(0xda, tagh, tagl, content)
243         sw = self.__reader.send_cmd(cmd_data)
244         if len(sw) != 2:
245             raise ValueError(sw)
246         if not (sw[0] == 0x90 and sw[1] == 0x00):
247             raise ValueError("%02x%02x" % (sw[0], sw[1]))
248         return True
249
250     def cmd_put_data_odd(self, tagh, tagl, content):
251         if self.__reader.is_tpdu_reader():
252             cmd_data = iso7816_compose(0xdb, tagh, tagl, content)
253             sw = self.__reader.send_cmd(cmd_data)
254         else:
255             cmd_data0 = iso7816_compose(0xdb, tagh, tagl, content[:128], 0x10)
256             cmd_data1 = iso7816_compose(0xdb, tagh, tagl, content[128:])
257             sw = self.__reader.send_cmd(cmd_data0)
258             if len(sw) != 2:
259                 raise ValueError(sw)
260             if not (sw[0] == 0x90 and sw[1] == 0x00):
261                 raise ValueError("%02x%02x" % (sw[0], sw[1]))
262             sw = self.__reader.send_cmd(cmd_data1)
263         if len(sw) != 2:
264             raise ValueError(sw)
265         if not (sw[0] == 0x90 and sw[1] == 0x00):
266             raise ValueError("%02x%02x" % (sw[0], sw[1]))
267         return True
268
269     def cmd_reset_retry_counter(self, how, who, data):
270         cmd_data = iso7816_compose(0x2c, how, who, data)
271         sw = self.__reader.send_cmd(cmd_data)
272         if len(sw) != 2:
273             raise ValueError(sw)
274         if not (sw[0] == 0x90 and sw[1] == 0x00):
275             raise ValueError("%02x%02x" % (sw[0], sw[1]))
276         return True
277
278     def cmd_pso(self, p1, p2, data):
279         if self.__reader.is_tpdu_reader():
280             cmd_data = iso7816_compose(0x2a, p1, p2, data, le=256)
281             r = self.__reader.send_cmd(cmd_data)
282             if len(r) < 2:
283                 raise ValueError(r)
284             sw = r[-2:]
285             r = r[0:-2]
286             if sw[0] == 0x61:
287                 return self.cmd_get_response(sw[1])
288             elif sw[0] == 0x90 and sw[1] == 0x00:
289                 return r
290             else:
291                 raise ValueError("%02x%02x" % (sw[0], sw[1]))
292         else:
293             if len(data) > 128:
294                 cmd_data0 = iso7816_compose(0x2a, p1, p2, data[:128], 0x10)
295                 cmd_data1 = iso7816_compose(0x2a, p1, p2, data[128:])
296                 sw = self.__reader.send_cmd(cmd_data0)
297                 if len(sw) != 2:
298                     raise ValueError(sw)
299                 if not (sw[0] == 0x90 and sw[1] == 0x00):
300                     raise ValueError("%02x%02x" % (sw[0], sw[1]))
301                 sw = self.__reader.send_cmd(cmd_data1)
302                 if len(sw) != 2:
303                     raise ValueError(sw)
304                 elif sw[0] != 0x61:
305                     raise ValueError("%02x%02x" % (sw[0], sw[1]))
306                 return self.cmd_get_response(sw[1])
307             else:
308                 cmd_data = iso7816_compose(0x2a, p1, p2, data)
309                 sw = self.__reader.send_cmd(cmd_data)
310                 if len(sw) != 2:
311                     raise ValueError(sw)
312                 if sw[0] == 0x90 and sw[1] == 0x00:
313                     return b""
314                 elif sw[0] != 0x61:
315                     raise ValueError("%02x%02x" % (sw[0], sw[1]))
316                 return self.cmd_get_response(sw[1])
317
318     def cmd_internal_authenticate(self, data):
319         if self.__reader.is_tpdu_reader():
320             cmd_data = iso7816_compose(0x88, 0, 0, data, le=256)
321         else:
322             cmd_data = iso7816_compose(0x88, 0, 0, data)
323         r = self.__reader.send_cmd(cmd_data)
324         if len(r) < 2:
325             raise ValueError(r)
326         sw = r[-2:]
327         r = r[0:-2]
328         if sw[0] == 0x61:
329             return self.cmd_get_response(sw[1])
330         elif sw[0] == 0x90 and sw[1] == 0x00:
331             return r
332         else:
333             raise ValueError("%02x%02x" % (sw[0], sw[1]))
334
335     def cmd_genkey(self, keyno):
336         if keyno == 1:
337             data = b'\xb6\x00'
338         elif keyno == 2:
339             data = b'\xb8\x00'
340         else:
341             data = b'\xa4\x00'
342         cmd_data = iso7816_compose(0x47, 0x80, 0, data)
343         sw = self.__reader.send_cmd(cmd_data)
344         if len(sw) != 2:
345             raise ValueError(sw)
346         if sw[0] == 0x90 and sw[1] == 0x00:
347             return b""
348         elif sw[0] != 0x61:
349             raise ValueError("%02x%02x" % (sw[0], sw[1]))
350         pk = self.cmd_get_response(sw[1])
351         return (pk[9:9+256], pk[9+256+2:9+256+2+3])
352
353     def cmd_get_public_key(self, keyno):
354         if keyno == 1:
355             data = b'\xb6\x00'
356         elif keyno == 2:
357             data = b'\xb8\x00'
358         else:
359             data = b'\xa4\x00'
360         if self.__reader.is_tpdu_reader():
361             cmd_data = iso7816_compose(0x47, 0x81, 0, data, le=512)
362             r = self.__reader.send_cmd(cmd_data)
363         else:
364             cmd_data = iso7816_compose(0x47, 0x81, 0, data)
365             r = self.__reader.send_cmd(cmd_data)
366         if len(r) < 2:
367             raise ValueError(r)
368         sw = r[-2:]
369         r = r[0:-2]
370         if sw[0] == 0x61:
371             pk = self.cmd_get_response(sw[1])
372         elif sw[0] == 0x90 and sw[1] == 0x00:
373             pk = r
374         else:
375             raise ValueError("%02x%02x" % (sw[0], sw[1]))
376         return pk
377
378     def cmd_put_data_remove(self, tagh, tagl):
379         cmd_data = iso7816_compose(0xda, tagh, tagl, b"")
380         sw = self.__reader.send_cmd(cmd_data)
381         if sw[0] != 0x90 and sw[1] != 0x00:
382             raise ValueError("%02x%02x" % (sw[0], sw[1]))
383
384     def cmd_put_data_key_import_remove(self, keyno):
385         if keyno == 1:
386             keyspec = b"\xb6\x00"      # SIG
387         elif keyno == 2:
388             keyspec = b"\xb8\x00"      # DEC
389         else:
390             keyspec = b"\xa4\x00"      # AUT
391         cmd_data = iso7816_compose(0xdb, 0x3f, 0xff, b"\x4d\x02" +  keyspec)
392         sw = self.__reader.send_cmd(cmd_data)
393         if sw[0] != 0x90 and sw[1] != 0x00:
394             raise ValueError("%02x%02x" % (sw[0], sw[1]))
395
396     def cmd_get_challenge(self):
397         cmd_data = iso7816_compose(0x84, 0x00, 0x00, '')
398         sw = self.__reader.send_cmd(cmd_data)
399         if len(sw) != 2:
400             raise ValueError(sw)
401         if sw[0] != 0x61:
402             raise ValueError("%02x%02x" % (sw[0], sw[1]))
403         return self.cmd_get_response(sw[1])
404
405     def cmd_external_authenticate(self, keyno, signed):
406         cmd_data = iso7816_compose(0x82, 0x00, keyno, signed[0:128], cls=0x10)
407         sw = self.__reader.send_cmd(cmd_data)
408         if len(sw) != 2:
409             raise ValueError(sw)
410         if not (sw[0] == 0x90 and sw[1] == 0x00):
411             raise ValueError("%02x%02x" % (sw[0], sw[1]))
412         cmd_data = iso7816_compose(0x82, 0x00, keyno, signed[128:])
413         sw = self.__reader.send_cmd(cmd_data)
414         if len(sw) != 2:
415             raise ValueError(sw)
416         if not (sw[0] == 0x90 and sw[1] == 0x00):
417             raise ValueError("%02x%02x" % (sw[0], sw[1]))
418
419 def parse_kdf_data(kdf_data):
420     if len(kdf_data) == 90:
421         single_salt = True
422     elif len(kdf_data) == 110:
423         single_salt = False
424     else:
425         raise ValueError("length does not much", kdf_data)
426
427     if kdf_data[0:2] != b'\x81\x01':
428         raise ValueError("data does not much")
429     algo = kdf_data[2]
430     if kdf_data[3:5] != b'\x82\x01':
431         raise ValueError("data does not much")
432     subalgo = kdf_data[5]
433     if kdf_data[6:8] != b'\x83\x04':
434         raise ValueError("data does not much")
435     iters = unpack(">I", kdf_data[8:12])[0]
436     if kdf_data[12:14] != b'\x84\x08':
437         raise ValueError("data does not much")
438     salt = kdf_data[14:22]
439     if single_salt:
440         salt_reset = None
441         salt_admin = None
442         if kdf_data[22:24] != b'\x87\x20':
443             raise ValueError("data does not much")
444         hash_user = kdf_data[24:56]
445         if kdf_data[56:58] != b'\x88\x20':
446             raise ValueError("data does not much")
447         hash_admin = kdf_data[58:90]
448     else:
449         if kdf_data[22:24] != b'\x85\x08':
450             raise ValueError("data does not much")
451         salt_reset = kdf_data[24:32]
452         if kdf_data[32:34] != b'\x86\x08':
453             raise ValueError("data does not much")
454         salt_admin = kdf_data[34:42]
455         if kdf_data[42:44] != b'\x87\x20':
456             raise ValueError("data does not much")
457         hash_user = kdf_data[44:76]
458         if kdf_data[76:78] != b'\x88\x20':
459             raise ValueError("data does not much")
460         hash_admin = kdf_data[78:110]
461     return ( algo, subalgo, iters, salt, salt_reset, salt_admin,
462              hash_user, hash_admin )