diff --git a/tool/pinpad-test.py b/tool/pinpad-test.py index b99aacb..ca808e2 100755 --- a/tool/pinpad-test.py +++ b/tool/pinpad-test.py @@ -30,14 +30,20 @@ from smartcard.CardType import AnyCardType from smartcard.CardRequest import CardRequest from smartcard.util import toHexString +from getpass import getpass + CM_IOCTL_GET_FEATURE_REQUEST = (0x42000000 + 3400) FEATURE_VERIFY_PIN_DIRECT = 0x06 FEATURE_MODIFY_PIN_DIRECT = 0x07 BY_ADMIN = 3 BY_USER = 1 +PIN_MIN_DEFAULT = 6 # min of OpenPGP card PIN_MAX_DEFAULT = 15 # max of VASCO DIGIPASS 920 +def s2l(s): + return [ ord(c) for c in s ] + def confirm_pin_setting(single_step): if single_step: return 0x01 # bConfirmPIN: new PIN twice @@ -45,7 +51,7 @@ def confirm_pin_setting(single_step): return 0x03 # bConfirmPIN: old PIN and new PIN twice class Card(object): - def __init__(self, add_a_byte, pinmax): + def __init__(self, add_a_byte, pinmin, pinmax): cardtype = AnyCardType() cardrequest = CardRequest(timeout=1, cardType=cardtype) cardservice = cardrequest.waitforcard() @@ -53,6 +59,7 @@ class Card(object): self.verify_ioctl = -1 self.modify_ioctl = -1 self.another_byte = add_a_byte + self.pinmin = pinmin self.pinmax = pinmax def get_features(self): @@ -97,7 +104,7 @@ class Card(object): 0x00, # bmPINBlockString 0x00, # bmPINLengthFormat self.pinmax, # wPINMaxExtraDigit Low (PINmax) - 1, # wPINMaxExtraDigit High (PINmin) + self.pinmin, # wPINMaxExtraDigit High (PINmin) 0x02, # bEntryValidationCondition 0x01, # bNumberMessage 0x00, # wLangId Low @@ -126,7 +133,7 @@ class Card(object): 0x00, # bInsertionOffsetOld 0x00, # bInsertionOffsetNew self.pinmax, # wPINMaxExtraDigit Low (PINmax) - 1, # wPINMaxExtraDigit High (PINmin) + self.pinmin, # wPINMaxExtraDigit High (PINmin) confirm_pin_setting(single_step), 0x02, # bEntryValidationCondition 0x03, # bNumberMessage @@ -147,13 +154,29 @@ class Card(object): if not (sw1 == 0x90 and sw2 == 0x00): raise ValueError, ("%s %02x %02x" % (command, sw1, sw2)) + def cmd_reset_retry_counter(self, who, data): + if who == BY_ADMIN: + apdu = [0x00, 0x2c, 0x02, 0x81, len(data) ] + data # BY_ADMIN + else: + apdu = [0x00, 0x2c, 0x00, 0x81, len(data) ] + data # BY_USER with resetcode + response, sw1, sw2 = self.connection.transmit(apdu) + if not (sw1 == 0x90 and sw2 == 0x00): + raise ValueError, ("cmd_reset_retry_counter %02x %02x" % (sw1, sw2)) + # Note: CCID specification doesn't permit this (only 0x20 and 0x24) def cmd_reset_retry_counter_pinpad(self, who): if who == BY_ADMIN: apdu = [0x00, 0x2c, 0x02, 0x81] # BY_ADMIN + self.send_modify_pinpad(apdu, True, "cmd_reset_retry_counter_pinpad") else: apdu = [0x00, 0x2c, 0x00, 0x81] # BY_USER with resetcode - self.send_modify_pinpad(apdu, False, "cmd_reset_retry_counter_pinpad") + self.send_modify_pinpad(apdu, False, "cmd_reset_retry_counter_pinpad") + + def cmd_put_resetcode(self, data): + apdu = [0x00, 0xda, 0x00, 0xd3, len(data) ] + data # BY_ADMIN + response, sw1, sw2 = self.connection.transmit(apdu) + if not (sw1 == 0x90 and sw2 == 0x00): + raise ValueError, ("cmd_put_resetcode %02x %02x" % (sw1, sw2)) # Note: CCID specification doesn't permit this (only 0x20 and 0x24) def cmd_put_resetcode_pinpad(self): @@ -168,11 +191,8 @@ class Card(object): self.send_modify_pinpad(apdu, is_exchange, "cmd_change_reference_data_pinpad") -# "Vasco DIGIPASS 920 [CCID] 00 00" -# "FSIJ Gnuk (0.16-34006F06) 00 00" - -def main(who, method, add_a_byte, pinmax, change_by_two_steps): - card = Card(add_a_byte, pinmax) +def main(who, method, add_a_byte, pinmin, pinmax, change_by_two_steps): + card = Card(add_a_byte, pinmin, pinmax) card.connection.connect() print "Reader/Token:", card.connection.getReader() @@ -208,20 +228,38 @@ def main(who, method, add_a_byte, pinmax, change_by_two_steps): print "and New Admin's PIN twice" card.cmd_change_reference_data_pinpad(who, False) elif method == "unblock": - # It's always by single step - if who == BY_USER: - print "Please input reset code" - print "and New User's PIN twice" + if change_by_two_steps: + # It means using keyboard for new PIN + if who == BY_USER: + resetcode=s2l(getpass("Please input reset code from keyboard: ")) + newpin=s2l(getpass("Please input New User's PIN from keyboard: ")) + card.cmd_reset_retry_counter(who,resetcode+newpin) + else: + print "Please input Admin's PIN" + card.cmd_verify_pinpad(BY_ADMIN) + newpin=s2l(getpass("Please input New User's PIN from keyboard: ")) + card.cmd_reset_retry_counter(who,newpin) + else: + if who == BY_USER: + print "Please input reset code" + print "and New User's PIN twice" + else: + print "Please input Admin's PIN" + card.cmd_verify_pinpad(BY_ADMIN) + print "Please input New User's PIN twice" + card.cmd_reset_retry_counter_pinpad(who) + elif method == "put": + if change_by_two_steps: + # It means using keyboard for new PIN + print "Please input Admin's PIN" + card.cmd_verify_pinpad(BY_ADMIN) + resetcode=s2l(getpass("Please input New Reset Code from keyboard: ")) + card.cmd_put_resetcode(resetcode) else: print "Please input Admin's PIN" - print "and New User's PIN twice" - card.cmd_reset_retry_counter_pinpad(who) - elif method == "put": - # It's always by two steps - print "Please input Admin's PIN" - card.cmd_verify_pinpad(BY_ADMIN) - print "Please input New Reset Code twice" - card.cmd_put_resetcode_pinpad() + card.cmd_verify_pinpad(BY_ADMIN) + print "Please input New Reset Code twice" + card.cmd_put_resetcode_pinpad() else: raise ValueError, method card.connection.disconnect() @@ -238,10 +276,13 @@ def print_usage(): print "\t--change:\tchange PIN (old PIN, new PIN twice)" print "\t--change2:\tchange PIN by two steps (old PIN, new PIN twice)" print "\t--unblock:\tunblock PIN (admin PIN/resetcode, new PIN twice)" + print "\t--unblock2:\tunblock PIN (admin PIN:pinpad, new PIN:kbd)" print "\t--put:\t\tsetup resetcode (admin PIN, new PIN twice)" + print "\t--put2::\t\tsetup resetcode (admin PIN:pinpad, new PIN:kbd)" print " options:" print "\t--admin:\tby administrator\t\t\t[False]" print "\t--add:\t\tadd a dummy byte at the end of APDU\t[False]" + print "\t--pinmin:\tspecify minimum length of PIN\t\t[6]" print "\t--pinmax:\tspecify maximum length of PIN\t\t[15]" print "EXAMPLES:" print " $ pinpad-test # verify user's PIN " @@ -258,6 +299,7 @@ if __name__ == '__main__': who = BY_USER method = "verify" add_a_byte = False + pinmin = PIN_MIN_DEFAULT pinmax = PIN_MAX_DEFAULT change_by_two_steps = False while len(sys.argv) >= 2: @@ -272,13 +314,22 @@ if __name__ == '__main__': change_by_two_steps = True elif option == '--unblock': method = "unblock" + elif option == '--unblock2': + method = "unblock" + change_by_two_steps = True elif option == '--add': add_a_byte = True + elif option == '--pinmin': + pinmin = int(sys.argv[1]) + sys.argv.pop(1) elif option == '--pinmax': pinmax = int(sys.argv[1]) sys.argv.pop(1) elif option == '--put': method = "put" + elif option == '--put2': + method = "put" + change_by_two_steps = True elif option == "verify": method = "verify" elif option == '--help': @@ -286,11 +337,33 @@ if __name__ == '__main__': exit(0) else: raise ValueError, option - main(who, method, add_a_byte, pinmax, change_by_two_steps) + main(who, method, add_a_byte, pinmin, pinmax, change_by_two_steps) # Failure # 67 00: Wrong length; no further indication # 69 82: Security status not satisfied: pin doesn't match # 69 85: Conditions of use not satisfied # 6b 00: Wrong parameters P1-P2 +# 6b 80 +# 64 02: PIN different +# "FSIJ Gnuk (0.16-34006F06) 00 00" +# Works well except --change2 + +# "Vasco DIGIPASS 920 [CCID] 00 00" +# OK: --verify +# OK: --verify --admin +# OK: --change +# OK: --change --admin +# FAIL: --change2 fails with 6b 00 +# FAIL: --change2 --admin fails with 6b 00 +# FAIL: --put fails with 6b 80 +# OK: --put2 +# OK: --unblock +# FAIL: --unblock --admin fails with 69 85 +# FAIL: --unblock2 fails with 69 85 +# FAIL: --unblock2 --admin fails with 69 85 + +# Gemalto GemPC Pinpad 00 00 +# It asks users PIN with --add but it results 67 00 +# It seems that it doesn't support variable length PIN