stlinkv2.py now works with newer PyUSB
[gnuk/gnuk.git] / tool / pinpadtest.py
1 #! /usr/bin/python
2
3 """
4 pinpadtest.py - a tool to test variable length pin entry with pinpad
5
6 Copyright (C) 2011, 2012, 2013 Free Software Initiative of Japan
7 Author: NIIBE Yutaka <gniibe@fsij.org>
8
9 This file is a part of Gnuk, a GnuPG USB Token implementation.
10
11 Gnuk is free software: you can redistribute it and/or modify it
12 under the terms of the GNU General Public License as published by
13 the Free Software Foundation, either version 3 of the License, or
14 (at your option) any later version.
15
16 Gnuk is distributed in the hope that it will be useful, but WITHOUT
17 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
18 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
19 License for more details.
20
21 You should have received a copy of the GNU General Public License
22 along with this program.  If not, see <http://www.gnu.org/licenses/>.
23 """
24
25 import sys
26
27 # Assume only single CCID device is attached to computer with card
28
29 from smartcard.CardType import AnyCardType
30 from smartcard.CardRequest import CardRequest
31 from smartcard.util import toHexString
32
33 from getpass import getpass
34
35 CM_IOCTL_GET_FEATURE_REQUEST = (0x42000000 + 3400)
36 CM_IOCTL_VENDOR_IFD_EXCHANGE = (0x42000000 + 1)
37 FEATURE_VERIFY_PIN_DIRECT    = 0x06
38 FEATURE_MODIFY_PIN_DIRECT    = 0x07
39
40 BY_ADMIN = 3
41 BY_USER = 1
42 PIN_MIN_DEFAULT = 6             # min of OpenPGP card
43 PIN_MAX_DEFAULT = 15            # max of VASCO DIGIPASS 920
44
45 def s2l(s):
46     return [ ord(c) for c in s ]
47
48 def confirm_pin_setting(single_step):
49     if single_step:
50         return 0x01    # bConfirmPIN: new PIN twice
51     else:
52         return 0x03    # bConfirmPIN: old PIN and new PIN twice
53
54 class Card(object):
55     def __init__(self, add_a_byte, pinmin, pinmax, fixed):
56         cardtype = AnyCardType()
57         cardrequest = CardRequest(timeout=10, cardType=cardtype)
58         cardservice = cardrequest.waitforcard()
59         self.connection = cardservice.connection
60         self.verify_ioctl = -1
61         self.modify_ioctl = -1
62         self.another_byte = add_a_byte
63         self.pinmin = pinmin
64         self.pinmax = pinmax
65         self.fixed = fixed
66
67     def get_features(self):
68         p = self.connection.control(CM_IOCTL_GET_FEATURE_REQUEST, [])
69         i = 0
70         while i < len(p):
71             code = p[i]
72             l = p[i+1]
73             i = i + 2
74             if l == 4:
75                 ioctl = (p[i] << 24) | (p[i+1] << 16) | (p[i+2] << 8) | p[i+3]
76                 i = i + l
77             else:
78                 i = i + l
79                 continue
80             if code == FEATURE_VERIFY_PIN_DIRECT:
81                 self.verify_ioctl = ioctl
82             elif code == FEATURE_MODIFY_PIN_DIRECT:
83                 self.modify_ioctl = ioctl
84         if self.verify_ioctl == -1:
85             raise ValueError, "Not supported"
86
87     def cmd_select_openpgp(self):
88         apdu = [0x00, 0xa4, 0x04, 0x00, 6, 0xd2, 0x76, 0x00, 0x01, 0x24, 0x01 ]
89         response, sw1, sw2 = self.connection.transmit(apdu)
90         if sw1 == 0x61:         # More data
91             response, sw1, sw2 = self.connection.transmit([0x00, 0xc0, 0, 0, sw2])
92         elif not (sw1 == 0x90 and sw2 == 0x00):
93             raise ValueError, ("cmd_select_openpgp %02x %02x" % (sw1, sw2))
94
95     def possibly_add_dummy_byte(self):
96         if self.another_byte:
97             return [ 0 ]
98         else:
99             return []
100
101     def cmd_vega_alpha_disable_empty_verify(self):
102         apdu = [ 0xB5, # -|
103                  0x01, #  | Pre-command parameters
104                  0x00, # -|
105                  0x03, # retry counter value (fixed value)
106                  0x00  # enable 3s timeout
107                  ]
108         data = self.connection.control(CM_IOCTL_VENDOR_IFD_EXCHANGE, apdu)
109
110     def cmd_verify_pinpad(self, who):
111         apdu = [0x00, 0x20, 0x00, 0x80+who ]
112         pin_verify = [ 0x00,    # bTimeOut
113                        0x00,    # bTimeOut2
114                        0x82,    # bmFormatString: Byte, pos=0, left, ASCII.
115                        self.fixed,    # bmPINBlockString
116                        0x00,    # bmPINLengthFormat
117                        self.pinmax, # wPINMaxExtraDigit Low  (PINmax)
118                        self.pinmin, # wPINMaxExtraDigit High (PINmin) 
119                        0x02,    # bEntryValidationCondition
120                        0x01,    # bNumberMessage
121                        0x00,    # wLangId Low
122                        0x00,    # wLangId High
123                        0x00,    # bMsgIndex
124                        0x00,    # bTeoPrologue[0]
125                        0x00,    # bTeoPrologue[1]
126                        ]
127         if self.fixed > 0:
128             apdu += [ self.fixed ]
129             apdu += [ 255 ] * self.fixed
130         else:
131             apdu += self.possibly_add_dummy_byte()
132         pin_verify += [ len(apdu) ]    # bTeoPrologue[2]
133         pin_verify += [ len(apdu), 0, 0, 0 ] + apdu
134         data = self.connection.control(self.verify_ioctl,pin_verify)
135         sw1 = data[0]
136         sw2 = data[1]
137         if not (sw1 == 0x90 and sw2 == 0x00):
138             raise ValueError, ("cmd_verify_pinpad %02x %02x" % (sw1, sw2))
139
140     def send_modify_pinpad(self, apdu, single_step, command):
141         if self.modify_ioctl == -1:
142             raise ValueError, "Not supported"
143         pin_modify = [ 0x00, # bTimerOut
144                        0x00, # bTimerOut2
145                        0x82, # bmFormatString: Byte, pos=0, left, ASCII.
146                        self.fixed, # bmPINBlockString
147                        0x00, # bmPINLengthFormat
148                        0x00, # bInsertionOffsetOld
149                        self.fixed, # bInsertionOffsetNew
150                        self.pinmax, # wPINMaxExtraDigit Low  (PINmax)
151                        self.pinmin, # wPINMaxExtraDigit High (PINmin) 
152                        confirm_pin_setting(single_step),
153                        0x02,    # bEntryValidationCondition
154                        0x03,    # bNumberMessage
155                        0x00,    # wLangId Low
156                        0x00,    # wLangId High
157                        0x00,    # bMsgIndex1
158                        0x01,    # bMsgIndex2
159                        0x02,    # bMsgIndex3
160                        0x00,    # bTeoPrologue[0]
161                        0x00,    # bTeoPrologue[1]
162                        ]
163         if self.fixed > 0:
164             apdu += [ 2*self.fixed ]
165             apdu += [ 255 ] * (2*self.fixed)
166         else:
167             apdu += self.possibly_add_dummy_byte()
168         pin_modify += [ len(apdu) ]    # bTeoPrologue[2]
169         pin_modify += [ len(apdu), 0, 0, 0 ] + apdu
170         data = self.connection.control(self.modify_ioctl,pin_modify)
171         sw1 = data[0]
172         sw2 = data[1]
173         if not (sw1 == 0x90 and sw2 == 0x00):
174             raise ValueError, ("%s %02x %02x" % (command, sw1, sw2))
175
176     def cmd_reset_retry_counter(self, who, data):
177         if who == BY_ADMIN:
178             apdu = [0x00, 0x2c, 0x02, 0x81, len(data) ] + data # BY_ADMIN
179         else:
180             apdu = [0x00, 0x2c, 0x00, 0x81, len(data) ] + data # BY_USER with resetcode
181         response, sw1, sw2 = self.connection.transmit(apdu)
182         if not (sw1 == 0x90 and sw2 == 0x00):
183             raise ValueError, ("cmd_reset_retry_counter %02x %02x" % (sw1, sw2))
184
185     # Note: CCID specification doesn't permit this (only 0x20 and 0x24)
186     def cmd_reset_retry_counter_pinpad(self, who):
187         if who == BY_ADMIN:
188             apdu = [0x00, 0x2c, 0x02, 0x81] # BY_ADMIN
189             self.send_modify_pinpad(apdu, True, "cmd_reset_retry_counter_pinpad")
190         else:
191             apdu = [0x00, 0x2c, 0x00, 0x81] # BY_USER with resetcode
192             self.send_modify_pinpad(apdu, False, "cmd_reset_retry_counter_pinpad")
193
194     def cmd_put_resetcode(self, data):
195         apdu = [0x00, 0xda, 0x00, 0xd3, len(data) ] + data # BY_ADMIN
196         response, sw1, sw2 = self.connection.transmit(apdu)
197         if not (sw1 == 0x90 and sw2 == 0x00):
198             raise ValueError, ("cmd_put_resetcode %02x %02x" % (sw1, sw2))
199
200     # Note: CCID specification doesn't permit this (only 0x20 and 0x24)
201     def cmd_put_resetcode_pinpad(self):
202         apdu = [0x00, 0xda, 0x00, 0xd3]
203         self.send_modify_pinpad(apdu, True, "cmd_put_resetcode_pinpad")
204
205     def cmd_change_reference_data_pinpad(self, who, is_exchange):
206         if is_exchange:
207             apdu = [0x00, 0x24, 1, 0x80+who]
208         else:
209             apdu = [0x00, 0x24, 0x00, 0x80+who]
210         self.send_modify_pinpad(apdu, is_exchange,
211                                 "cmd_change_reference_data_pinpad")
212
213 COVADIS_VEGA_ALPHA="COVADIS VEGA-ALPHA (000000F5) 00 00"
214 # We need to set ifdDriverOptions in /etc/libccid_Info.plist:
215 #
216 #       <key>ifdDriverOptions</key>
217 #       <string>0x0001</string>
218 #
219 #       1: DRIVER_OPTION_CCID_EXCHANGE_AUTHORIZED
220 #               the CCID Exchange command is allowed. You can use it through
221 #               SCardControl(hCard, IOCTL_SMARTCARD_VENDOR_IFD_EXCHANGE, ...)
222
223 def main(who, method, add_a_byte, pinmin, pinmax, change_by_two_steps, fixed):
224     card = Card(add_a_byte, pinmin, pinmax, fixed)
225     card.connection.connect()
226
227     ident = card.connection.getReader()
228     print "Reader/Token:", ident
229     print "ATR:", toHexString( card.connection.getATR() )
230
231     if ident == COVADIS_VEGA_ALPHA:
232         card.cmd_vega_alpha_disable_empty_verify()
233
234     card.get_features()
235
236     card.cmd_select_openpgp()
237     if method == "verify":
238         if who == BY_USER:
239             print "Please input User's PIN"
240         else:
241             print "Please input Admin's PIN"
242         card.cmd_verify_pinpad(who)
243     elif method == "change":
244         if change_by_two_steps:
245             if who == BY_USER:
246                 print "Please input User's PIN"
247             else:
248                 print "Please input Admin's PIN"
249             card.cmd_verify_pinpad(who)
250             if who == BY_USER:
251                 print "Please input New User's PIN twice"
252             else:
253                 print "Please input New Admin's PIN twice"
254             card.cmd_change_reference_data_pinpad(who, True)
255         else:
256             if who == BY_USER:
257                 print "Please input User's PIN"
258                 print "and New User's PIN twice"
259             else:
260                 print "Please input Admin's PIN"
261                 print "and New Admin's PIN twice"
262             card.cmd_change_reference_data_pinpad(who, False)
263     elif method == "unblock":
264         if change_by_two_steps:
265             # It means using keyboard for new PIN
266             if who == BY_USER:
267                 resetcode=s2l(getpass("Please input reset code from keyboard: "))
268                 newpin=s2l(getpass("Please input New User's PIN from keyboard: "))
269                 card.cmd_reset_retry_counter(who,resetcode+newpin)
270             else:
271                 print "Please input Admin's PIN"
272                 card.cmd_verify_pinpad(BY_ADMIN)
273                 newpin=s2l(getpass("Please input New User's PIN from keyboard: "))
274                 card.cmd_reset_retry_counter(who,newpin)
275         else:
276             if who == BY_USER:
277                 print "Please input reset code"
278                 print "and New User's PIN twice"
279             else:
280                 print "Please input Admin's PIN"
281                 card.cmd_verify_pinpad(BY_ADMIN)
282                 print "Please input New User's PIN twice"
283             card.cmd_reset_retry_counter_pinpad(who)
284     elif method == "put":
285         if change_by_two_steps:
286             # It means using keyboard for new PIN
287             print "Please input Admin's PIN"
288             card.cmd_verify_pinpad(BY_ADMIN)
289             resetcode=s2l(getpass("Please input New Reset Code from keyboard: "))
290             card.cmd_put_resetcode(resetcode)
291         else:
292             print "Please input Admin's PIN"
293             card.cmd_verify_pinpad(BY_ADMIN)
294             print "Please input New Reset Code twice"
295             card.cmd_put_resetcode_pinpad()
296     else:
297         raise ValueError, method
298     card.connection.disconnect()
299
300     print "OK."
301     return 0
302
303 def print_usage():
304     print "pinpad-test: testing pinentry of PC/SC card reader"
305     print "    help:"
306     print "\t--help:\t\tthis message"
307     print "    method:\t\t\t\t\t\t\t[verify]"
308     print "\t--verify:\tverify PIN"
309     print "\t--change:\tchange PIN (old PIN, new PIN twice)"
310     print "\t--change2:\tchange PIN by two steps (old PIN, new PIN twice)"
311     print "\t--unblock:\tunblock PIN (admin PIN/resetcode, new PIN twice)"
312     print "\t--unblock2:\tunblock PIN (admin PIN:pinpad, new PIN:kbd)"
313     print "\t--put:\t\tsetup resetcode (admin PIN, new PIN twice)"
314     print "\t--put2::\t\tsetup resetcode (admin PIN:pinpad, new PIN:kbd)"
315     print "    options:"
316     print "\t--fixed N:\tUse fixed length input"
317     print "\t--admin:\tby administrator\t\t\t[False]"
318     print "\t--add:\t\tadd a dummy byte at the end of APDU\t[False]"
319     print "\t--pinmin:\tspecify minimum length of PIN\t\t[6]"
320     print "\t--pinmax:\tspecify maximum length of PIN\t\t[15]"
321     print "EXAMPLES:"
322     print "   $ pinpad-test                   # verify user's PIN "
323     print "   $ pinpad-test --admin           # verify admin's PIN "
324     print "   $ pinpad-test --change          # change user's PIN "
325     print "   $ pinpad-test --change --admin  # change admin's PIN "
326     print "   $ pinpad-test --change2         # change user's PIN by two steps"
327     print "   $ pinpad-test --change2 --admin # change admin's PIN by two steps"
328     print "   $ pinpad-test --unblock         # change user's PIN by reset code"
329     print "   $ pinpad-test --unblock --admin # change user's PIN by admin's PIN"
330     print "   $ pinpad-test --put             # setup resetcode "
331
332 if __name__ == '__main__':
333     who = BY_USER
334     method = "verify"
335     add_a_byte = False
336     pinmin = PIN_MIN_DEFAULT
337     pinmax = PIN_MAX_DEFAULT
338     change_by_two_steps = False
339     fixed=0
340     while len(sys.argv) >= 2:
341         option = sys.argv[1]
342         sys.argv.pop(1)
343         if option == '--admin':
344             who = BY_ADMIN
345         elif option == '--change':
346             method = "change"
347         elif option == '--change2':
348             method = "change"
349             change_by_two_steps = True
350         elif option == '--unblock':
351             method = "unblock"
352         elif option == '--unblock2':
353             method = "unblock"
354             change_by_two_steps = True
355         elif option == '--add':
356             add_a_byte = True
357         elif option == '--fixed':
358             fixed = int(sys.argv[1])
359             sys.argv.pop(1)
360         elif option == '--pinmin':
361             pinmin = int(sys.argv[1])
362             sys.argv.pop(1)
363         elif option == '--pinmax':
364             pinmax = int(sys.argv[1])
365             sys.argv.pop(1)
366         elif option == '--put':
367             method = "put"
368         elif option == '--put2':
369             method = "put"
370             change_by_two_steps = True
371         elif option == "verify":
372             method = "verify"
373         elif option == '--help':
374             print_usage()
375             exit(0)
376         else:
377             raise ValueError, option
378     main(who, method, add_a_byte, pinmin, pinmax, change_by_two_steps, fixed)
379
380 # Failure
381 # 67 00: Wrong length; no further indication
382 # 69 82: Security status not satisfied: pin doesn't match
383 # 69 85: Conditions of use not satisfied
384 # 6b 00: Wrong parameters P1-P2
385 # 6b 80
386 # 64 02: PIN different
387
388 # General
389 # OpenPGP card v2 doesn't support CHANGE REFERENCE DATA in exchanging
390 # mode (with P1 == 01, replacing PIN).
391 #   FAIL: --change2 fails with 6b 00 (after input of PIN)
392 #   FAIL: --change2 --admin fails with 6b 00 (after input of PIN)
393
394 # "FSIJ Gnuk (0.16-34006F06) 00 00"
395 # Works well except --change2
396 # It could support --put and --unblock, but currently it's disabled.
397
398 # "Vasco DIGIPASS 920 [CCID] 00 00"
399 #   OK: --verify
400 #   OK: --verify --admin
401 #   OK: --change
402 #   OK: --change --admin
403 #   OK: --unblock
404 #   FAIL: --unblock --admin fails with 69 85   (after input of PIN)
405 #   FAIL: --put fails with 6b 80 (before input of resetcode)
406 #   OK: --put2
407 #   FAIL: --unblock2 fails with 69 85
408 #   FAIL: --unblock2 --admin fails with 69 85  (after input of PIN)
409
410 # 0c4b:0500 Reiner SCT Kartensysteme GmbH
411 # "REINER SCT cyberJack RFID standard (7592671050) 00 00"
412 #   OK: --verify
413 #   OK: --verify --admin
414 #   OK: --change
415 #   OK: --change --admin
416 #   OK: --unblock
417 #   OK: --unblock --admin
418 #   FAIL: --put fails with 69 85
419
420 # Gemalto GemPC Pinpad 00 00
421 # It asks users PIN with --add but it results 67 00
422 # It seems that it doesn't support variable length PIN
423 # Firmware version: GemTwRC2-V2.10-GL04
424
425 # 072f:90d2 Advanced Card Systems, Ltd
426 # ACS ACR83U 01 00
427 # --verify failed with 6b 80
428
429 # 08e6:34c2 Gemplus
430 # Gemalto Ezio Shield PinPad 01 00
431 # works well
432 #   FAIL: --unblock2 fails with 6d 00
433
434 # 076b:3821 OmniKey AG CardMan 3821
435 # OmniKey CardMan 3821 01 00
436 # Works well with --pinmax 31 --pinmin 1