├── examples ├── .keep ├── get-card-info │ ├── get_card_info_example.py │ └── README.md ├── add-callback │ ├── add_callback_example.py │ └── README.md ├── get-key-info │ ├── get_key_info_example.py │ └── README.md ├── generate-and-verify-signature │ ├── generate_and_verify_signature_example.py │ └── README.md └── modify-pin │ ├── modify_pin_example.py │ └── README.md ├── blocksec2go ├── cli │ ├── __init__.py │ ├── list_readers.py │ ├── generate_keypair.py │ ├── set_pin.py │ ├── disable_pin.py │ ├── change_pin.py │ ├── get_card_info.py │ ├── unlock_pin.py │ ├── encrypted_keyimport.py │ ├── get_key_info.py │ ├── generate_signature.py │ └── main.py ├── __main__.py ├── comm │ ├── __init__.py │ ├── card_observer.py │ ├── pyscard.py │ └── base.py ├── __init__.py ├── util.py └── commands.py ├── .gitignore ├── LICENSE ├── setup.py ├── README.md └── CONTRIBUTING.md /examples/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /blocksec2go/cli/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /blocksec2go/__main__.py: -------------------------------------------------------------------------------- 1 | from blocksec2go.cli.main import main 2 | main() -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | venv 3 | __pycache__ 4 | *.egg-info 5 | 6 | *.swp 7 | 8 | -------------------------------------------------------------------------------- /blocksec2go/comm/__init__.py: -------------------------------------------------------------------------------- 1 | from blocksec2go.comm.pyscard import open_pyscard 2 | from blocksec2go.comm.base import CardError 3 | from blocksec2go.comm.card_observer import observer -------------------------------------------------------------------------------- /blocksec2go/cli/list_readers.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import json 3 | import argparse 4 | 5 | import smartcard.System 6 | 7 | def _list_readers(args): 8 | readers = smartcard.System.readers() 9 | if args.machine_readable: 10 | 11 | json.dump({'status': 'success', 'readers': [str(reader) for reader in readers]}, sys.stdout) 12 | else: 13 | for reader in readers: 14 | print(reader) 15 | 16 | def add_subcommand(subparsers): 17 | parser = subparsers.add_parser('list_readers', description='List PC/SC readers connected to the system') 18 | parser.set_defaults(func=_list_readers) 19 | -------------------------------------------------------------------------------- /blocksec2go/cli/generate_keypair.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import json 3 | import argparse 4 | 5 | from blocksec2go import open_pyscard, CardError 6 | from blocksec2go import select_app, generate_keypair 7 | 8 | def _generate_keypair(args): 9 | reader = open_pyscard(args.reader) 10 | select_app(reader) 11 | key_id = generate_keypair(reader) 12 | 13 | if args.machine_readable: 14 | json.dump({'status': 'success', 'key_id': key_id}, fp=sys.stdout) 15 | else: 16 | print('Key ID: {}'.format(key_id)) 17 | 18 | def add_subcommand(subparsers): 19 | parser = subparsers.add_parser('generate_keypair', description='Generate a new keypair') 20 | parser.set_defaults(func=_generate_keypair) 21 | -------------------------------------------------------------------------------- /blocksec2go/cli/set_pin.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import json 3 | import argparse 4 | 5 | from blocksec2go import open_pyscard, CardError 6 | from blocksec2go import select_app, set_pin 7 | 8 | def _set_pin(args): 9 | reader = open_pyscard(args.reader) 10 | select_app(reader) 11 | puk = set_pin(reader, args.pin) 12 | 13 | if args.machine_readable: 14 | json.dump({ 15 | 'status': 'success', 16 | 'puk': puk.hex()}, fp=sys.stdout) 17 | else: 18 | print('PUK to unlock card (hex): ' + puk.hex()) 19 | 20 | def add_subcommand(subparsers): 21 | parser = subparsers.add_parser('set_pin', description='Configure a PIN') 22 | parser.set_defaults(func=_set_pin) 23 | parser.add_argument('pin', help='new PIN') -------------------------------------------------------------------------------- /blocksec2go/cli/disable_pin.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import json 3 | import argparse 4 | 5 | from blocksec2go import open_pyscard, CardError 6 | from blocksec2go import select_app, change_pin, unlock_pin 7 | from blocksec2go.util import bytes_from_hex 8 | 9 | def _disable_pin(args): 10 | reader = open_pyscard(args.reader) 11 | select_app(reader) 12 | puk = change_pin(reader, args.pin, 'dummy') 13 | unlock_pin(reader, puk) 14 | 15 | if args.machine_readable: 16 | json.dump({'status': 'success'}, fp=sys.stdout) 17 | else: 18 | print('OK - unlocked') 19 | 20 | def add_subcommand(subparsers): 21 | parser = subparsers.add_parser('disable_pin', description='Disable PIN on a card') 22 | parser.set_defaults(func=_disable_pin) 23 | parser.add_argument('pin', help='current PIN') -------------------------------------------------------------------------------- /blocksec2go/cli/change_pin.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import json 3 | import argparse 4 | 5 | from blocksec2go import open_pyscard, CardError 6 | from blocksec2go import select_app, change_pin 7 | 8 | def _change_pin(args): 9 | reader = open_pyscard(args.reader) 10 | select_app(reader) 11 | puk = change_pin(reader, args.current_pin, args.new_pin) 12 | 13 | if args.machine_readable: 14 | json.dump({ 15 | 'status': 'success', 16 | 'puk': puk.hex()}, fp=sys.stdout) 17 | else: 18 | print('New PUK to unlock card (hex): ' + puk.hex()) 19 | 20 | def add_subcommand(subparsers): 21 | parser = subparsers.add_parser('change_pin', description='Change the currently configured PIN') 22 | parser.set_defaults(func=_change_pin) 23 | parser.add_argument('current_pin', help='current PIN') 24 | parser.add_argument('new_pin', help='new PIN') -------------------------------------------------------------------------------- /blocksec2go/cli/get_card_info.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import json 3 | import argparse 4 | 5 | from blocksec2go import open_pyscard, CardError 6 | from blocksec2go import select_app 7 | 8 | def _get_card_info(args): 9 | reader = open_pyscard(args.reader) 10 | 11 | (pin_active, card_id, version) = select_app(reader) 12 | 13 | if args.machine_readable: 14 | json.dump({ 15 | 'status': 'success', 16 | 'pin_active': pin_active, 17 | 'card_id': card_id.hex(), 18 | 'version': version}, fp=sys.stdout) 19 | else: 20 | print('PIN is: ' + ('ENABLED' if pin_active else 'disabled')) 21 | print('Card ID (hex): ' + card_id.hex()) 22 | print('Version: ' + version) 23 | 24 | def add_subcommand(subparsers): 25 | parser = subparsers.add_parser('get_card_info', description='Retrieve card information') 26 | parser.set_defaults(func=_get_card_info) -------------------------------------------------------------------------------- /blocksec2go/cli/unlock_pin.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import json 3 | import argparse 4 | 5 | from blocksec2go import open_pyscard, CardError 6 | from blocksec2go import select_app, unlock_pin 7 | from blocksec2go.util import bytes_from_hex 8 | 9 | def _unlock_pin(args): 10 | reader = open_pyscard(args.reader) 11 | select_app(reader) 12 | status = unlock_pin(reader, args.puk) 13 | if(True == status): 14 | if args.machine_readable: 15 | json.dump({'status': 'success'}, fp=sys.stdout) 16 | else: 17 | print('OK - unlocked') 18 | elif(0 != status): 19 | print('ERROR - ' + str(status) + ' tries left') 20 | else: 21 | print('ERROR - Locked') 22 | 23 | def add_subcommand(subparsers): 24 | parser = subparsers.add_parser('unlock_pin', description='Unlock a locked card') 25 | parser.set_defaults(func=_unlock_pin) 26 | parser.add_argument('puk', help='PUK to unlock card', type=bytes_from_hex()) -------------------------------------------------------------------------------- /blocksec2go/cli/encrypted_keyimport.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import json 3 | import argparse 4 | 5 | from blocksec2go import open_pyscard, CardError 6 | from blocksec2go import select_app, verify_pin, encrypted_keyimport 7 | from blocksec2go.util import bytes_from_hex 8 | 9 | def _encrypted_keyimport(args): 10 | reader = open_pyscard(args.reader) 11 | select_app(reader) 12 | if args.pin is not None: 13 | verify_pin(reader, args.pin) 14 | encrypted_keyimport(reader, args.seed) 15 | 16 | if args.machine_readable: 17 | json.dump({'status': 'success'}, fp=sys.stdout) 18 | else: 19 | print('OK - import succeeded') 20 | 21 | def add_subcommand(subparsers): 22 | parser = subparsers.add_parser('encrypted_keyimport', description='Create a reproducible key from a given seed') 23 | parser.set_defaults(func=_encrypted_keyimport) 24 | parser.add_argument('seed', help='seed used to generate key', type=bytes_from_hex(16)) 25 | parser.add_argument('--pin', help='PIN to use') -------------------------------------------------------------------------------- /blocksec2go/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | This library allows the user to communicate with the 3 | Infineon Blockchain Security2GO Starterkit through python 4 | or using a command line tool. 5 | 6 | Command Line: 7 | The command line tool installed is called ``blocksec2go``. 8 | It is built as a binary with subcommands for the individual 9 | commands to send to the card. 10 | To see the supported commands, run: 11 | 12 | $ blocksec2go -h 13 | 14 | Help for individual commands can be seen by running 15 | 16 | $ blocksec2go -h 17 | 18 | Library usage: 19 | To interact with the library, an object to communicate with 20 | the reader is needed. The library includes a wrapper for PC/SC 21 | devices (using PyScard), for different readers custom wrappers 22 | must be implemented. 23 | 24 | All supported commands can then be sent using the functions 25 | from the ``blocksec2go.commands`` module. 26 | """ 27 | from blocksec2go.commands import * 28 | from blocksec2go.comm import * -------------------------------------------------------------------------------- /examples/get-card-info/get_card_info_example.py: -------------------------------------------------------------------------------- 1 | import blocksec2go 2 | 3 | if('__main__' == __name__): 4 | reader = None 5 | reader_name = 'Identiv uTrust 3700 F' 6 | while(reader == None): 7 | try: 8 | reader = blocksec2go.find_reader(reader_name) 9 | print('Found the specified reader and a card!', end='\r') 10 | except Exception as details: 11 | if('No reader found' == str(details)): 12 | print('No card reader found! ', end='\r') 13 | elif('No card on reader' == str(details)): 14 | print('Found reader, but no card!', end='\r') 15 | else: 16 | print('ERROR:', details) 17 | raise SystemExit 18 | try: 19 | pin_active, card_id, version = blocksec2go.select_app(reader) 20 | print('Found the specified reader and a Blockchain Security 2Go card!') 21 | except Exception as details: 22 | print('ERROR:', details) 23 | raise SystemExit 24 | print('Is PIN enabled?', pin_active) 25 | print('Card ID (hex):', card_id.hex()) 26 | print('Version: ' + version) -------------------------------------------------------------------------------- /blocksec2go/comm/card_observer.py: -------------------------------------------------------------------------------- 1 | from smartcard.CardMonitoring import CardMonitor, CardObserver 2 | 3 | class card_observer(CardObserver): 4 | """ Monitors smartcard 5 | 6 | Monitors insertion or removal of smartcard. Calls functions in 7 | case of insertion/removal. 8 | """ 9 | def connect(self): 10 | pass 11 | 12 | def disconnect(self): 13 | pass 14 | 15 | def update(self, observable, actions): 16 | (addedcards, removedcards) = actions 17 | for card in addedcards: 18 | self.connect() 19 | for card in removedcards: 20 | self.disconnect() 21 | 22 | class observer: 23 | """ Wrapper for card observer 24 | 25 | Abstracts communication into simple functions so that the user 26 | doesn't have to communicate with ``CardMonitoring`` library 27 | """ 28 | def start(): 29 | cardmonitor = CardMonitor() 30 | cardobserver = card_observer() 31 | cardmonitor.addObserver(cardobserver) 32 | return (cardmonitor, cardobserver) 33 | 34 | def stop(cardmonitor, cardobserver): 35 | cardmonitor.deleteObserver(cardobserver) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Infineon Technologies AG 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /blocksec2go/cli/get_key_info.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import json 3 | import argparse 4 | 5 | from blocksec2go import open_pyscard, CardError 6 | from blocksec2go import select_app, get_key_info 7 | 8 | def _get_key_info(args): 9 | reader = open_pyscard(args.reader) 10 | select_app(reader) 11 | (global_counter, counter, key) = get_key_info(reader, args.key_id) 12 | 13 | if args.machine_readable: 14 | json.dump({ 15 | 'status': 'success', 16 | 'key_id': args.key_id, 17 | 'global_counter': global_counter, 18 | 'counter': counter, 19 | 'key': key.hex()}, fp=sys.stdout) 20 | else: 21 | print('Remaining signatures with card: {}'.format(global_counter)) 22 | print('Remaining signatures with key {}: {}'.format(args.key_id, counter)) 23 | print('Public key (hex, encoded according to SEC1): ' + key.hex()) 24 | 25 | def add_subcommand(subparsers): 26 | parser = subparsers.add_parser('get_key_info', description='Get public key and signature counters') 27 | parser.set_defaults(func=_get_key_info) 28 | parser.add_argument('key_id', help='ID of the key to use', type=int) 29 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | with open('README.md', 'r') as fh: 4 | long_description = fh.read() 5 | 6 | setup( 7 | name='blocksec2go', 8 | version='1.2', 9 | author='Infineon Technologies AG', 10 | author_email='blockchain@infineon.com', 11 | description='Allow for communication with Infineon\'s Blockchain Security 2Go starter kit', 12 | long_description=long_description, 13 | long_description_content_type='text/markdown', 14 | url='https://github.com/Infineon/BlockchainSecurity2Go-Python-Library', 15 | license='MIT', 16 | packages=find_packages(), 17 | install_requires=[ 18 | 'pyscard', 19 | 'cryptography' 20 | ], 21 | entry_points={ 22 | 'console_scripts': [ 23 | 'blocksec2go = blocksec2go.cli.main:main', 24 | ], 25 | }, 26 | classifiers=[ 27 | "Programming Language :: Python :: 3", 28 | "Development Status :: 3 - Alpha", 29 | "Environment :: Console", 30 | "Intended Audience :: Developers", 31 | "Intended Audience :: Education", 32 | "Topic :: Software Development :: Libraries", 33 | "License :: OSI Approved :: MIT License", 34 | "Operating System :: OS Independent", 35 | ], 36 | zip_safe=False 37 | ) 38 | -------------------------------------------------------------------------------- /blocksec2go/cli/generate_signature.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import json 3 | import argparse 4 | 5 | from blocksec2go import open_pyscard, CardError 6 | from blocksec2go import select_app, verify_pin, generate_signature 7 | from blocksec2go.util import bytes_from_hex 8 | 9 | def _generate_signature(args): 10 | reader = open_pyscard(args.reader) 11 | select_app(reader) 12 | if args.pin is not None: 13 | verify_pin(reader, args.pin) 14 | (global_counter, counter, signature) = generate_signature(reader, args.key_id, args.hash) 15 | 16 | if args.machine_readable: 17 | json.dump({ 18 | 'status': 'success', 19 | 'global_counter': global_counter, 20 | 'counter': counter, 21 | 'signature': signature.hex()}, fp=sys.stdout) 22 | else: 23 | print('Remaining signatures with card: {}'.format(global_counter)) 24 | print('Remaining signatures with key {}: {}'.format(args.key_id, counter)) 25 | print('Signature (hex): ' + signature.hex()) 26 | 27 | def add_subcommand(subparsers): 28 | parser = subparsers.add_parser('generate_signature', description='Sign a hash with specified key') 29 | parser.set_defaults(func=_generate_signature) 30 | parser.add_argument('key_id', help='key id', type=int) 31 | parser.add_argument('hash', help='hash to sign, in hex', type=bytes_from_hex(32)) 32 | parser.add_argument('--pin', help='PIN to use') -------------------------------------------------------------------------------- /blocksec2go/util.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | 3 | def bytes_from_hex(expected_len=None): 4 | """ Convert and check hex argument to bytes 5 | 6 | Returns a function that will convert a given hexadecimal string 7 | and convert it to ``bytes``. Function will check the length if 8 | requested. 9 | 10 | Args: 11 | expected_len (int): expected length of resulting byte string, ``None`` to not check 12 | 13 | Returns: 14 | :obj:`function`: function that converts and checks 15 | 16 | Args: 17 | string (:obj:`str`): hex string 18 | 19 | Returns: 20 | :obj:`bytes`: converted binary data 21 | 22 | Raises: 23 | argparse.ArgumentTypeException: For invalid hex encoding 24 | or invalid length. 25 | 26 | Example: 27 | 28 | >>> parser = argparse.ArgumentParser() 29 | >>> parser.add_argument('arg', type=bytes_from_hex(16)) 30 | """ 31 | def _bytes_from_hex(string): 32 | b = None 33 | try: 34 | b = bytes.fromhex(string) 35 | except Exception as e: 36 | raise argparse.ArgumentTypeError('could not parse "' + string + '" as hex-encoded value', e) 37 | 38 | if expected_len is not None and len(b) != expected_len: 39 | raise argparse.ArgumentTypeError('invalid length of {} bytes, must be {} bytes long'.format(len(b), expected_len)) 40 | 41 | return b 42 | 43 | return _bytes_from_hex -------------------------------------------------------------------------------- /examples/add-callback/add_callback_example.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from time import sleep 3 | import blocksec2go 4 | from blocksec2go.comm import observer 5 | 6 | def get_reader(): 7 | reader = None 8 | reader_name = 'Identiv uTrust 3700 F' 9 | while(reader == None): 10 | try: 11 | reader = blocksec2go.find_reader(reader_name) 12 | print('Found the specified reader and a card!', end='\r') 13 | except Exception as details: 14 | if('No reader found' == str(details)): 15 | print('No card reader found! ', end='\r') 16 | elif('No card on reader' == str(details)): 17 | print('Found reader, but no card!', end='\r') 18 | else: 19 | print('ERROR: ' + str(details)) 20 | raise SystemExit 21 | return reader 22 | 23 | def activate_card(reader): 24 | try: 25 | blocksec2go.select_app(reader) 26 | print('Found the specified reader and a Blockchain Security 2Go card!') 27 | except Exception as details: 28 | print('ERROR: ' + str(details)) 29 | raise SystemExit 30 | 31 | def connected(self): 32 | print('A smartcard is connected to the reader!') 33 | reader = get_reader() 34 | activate_card(reader) 35 | 36 | def disconnected(self): 37 | print('The card is disconnected from the reader!') 38 | 39 | if('__main__' == __name__): 40 | print("Insert a Blockchain Security 2Go card onto the reader!") 41 | cardmonitor, cardobserver = observer.start() 42 | sleep(1) 43 | blocksec2go.add_callback(connect = connected, disconnect = disconnected) 44 | print('Press Enter at any time to exit the program!') 45 | sys.stdin.read(1) 46 | observer.stop(cardmonitor, cardobserver) -------------------------------------------------------------------------------- /examples/get-key-info/get_key_info_example.py: -------------------------------------------------------------------------------- 1 | import blocksec2go 2 | 3 | def get_reader(): 4 | reader = None 5 | reader_name = 'Identiv uTrust 3700 F' 6 | while(reader == None): 7 | try: 8 | reader = blocksec2go.find_reader(reader_name) 9 | print('Found the specified reader and a card!', end='\r') 10 | except Exception as details: 11 | if('No reader found' == str(details)): 12 | print('No card reader found! ', end='\r') 13 | elif('No card on reader' == str(details)): 14 | print('Found reader, but no card!', end='\r') 15 | else: 16 | print('ERROR: ' + str(details)) 17 | raise SystemExit 18 | return reader 19 | 20 | def activate_card(reader): 21 | try: 22 | blocksec2go.select_app(reader) 23 | print('Found the specified reader and a Blockchain Security 2Go card!') 24 | except Exception as details: 25 | print('ERROR: ' + str(details)) 26 | raise SystemExit 27 | 28 | if('__main__' == __name__): 29 | reader = get_reader() 30 | activate_card(reader) 31 | 32 | try: 33 | if('Yes' == input('Do you want to create a new Keypair? ("Yes" or "No")\n')): 34 | key_id = blocksec2go.generate_keypair(reader) 35 | print('Keypair generated on slot ' + str(key_id)) 36 | except Exception as details: 37 | print('ERROR: ' + str(details)) 38 | raise SystemExit 39 | 40 | try: 41 | key_id = int(input('Get information on which key? (Number between 0-255 only)\n')) 42 | global_counter, counter, key = blocksec2go.get_key_info(reader, key_id) 43 | except Exception as details: 44 | print('ERROR: ' + str(details)) 45 | raise SystemExit 46 | print('Remaining signatures with card: ' + str(global_counter)) 47 | print('Remaining signatures with key' + str(key_id) + ': ' + str(counter)) 48 | print('Public key (hex, encoded according to SEC1): ' + key.hex()) -------------------------------------------------------------------------------- /blocksec2go/comm/pyscard.py: -------------------------------------------------------------------------------- 1 | import array 2 | import logging 3 | import smartcard.System 4 | from smartcard.pcsc.PCSCReader import PCSCReader 5 | from blocksec2go.comm.base import Apdu, ApduResponse 6 | 7 | logger = logging.getLogger(__name__) 8 | 9 | class PySCardReader: 10 | """ Wrapper to use PyScard with blocksec2go 11 | 12 | Abstracts communication into a simple function 13 | """ 14 | def __init__(self, connection): 15 | self.connection = connection 16 | self.connection.connect() 17 | 18 | def transceive(self, header, data = b'', le = -1): 19 | apdu = Apdu(header, data, le) 20 | logger.debug(apdu) 21 | resp = self._transceive(bytes(apdu)) 22 | logger.debug(resp) 23 | return resp 24 | 25 | def _transceive(self, data): 26 | resp, sw1, sw2 = self.connection.transmit(array.array('b', data).tolist()) 27 | return ApduResponse(array.array('B', resp).tobytes(), (sw1 << 8) + sw2) 28 | 29 | def open_pyscard(name=None): 30 | """ Open PC/SC reader using PyScard 31 | If no reader name is given, try to open all available readers, 32 | the first one that succeeds is chosen. 33 | 34 | Args: 35 | name (:obj:`str`): name of the reader as registered in the system 36 | 37 | Returns: 38 | :obj:`PyScardReader`: PyScard wrapper object 39 | 40 | Raises: 41 | RuntimeError exception if no reader can be opened 42 | Various PyScard exceptions 43 | """ 44 | if name is not None: 45 | return PySCardReader(PCSCReader(name).createConnection()) 46 | else: 47 | readers = smartcard.System.readers() 48 | for reader in readers: 49 | try: 50 | pyscardreader = PySCardReader(PCSCReader(reader).createConnection()) 51 | except: 52 | pass 53 | else: 54 | logger.debug("Open reader: %s", reader) 55 | return pyscardreader 56 | 57 | raise RuntimeError('No reader with card found. Available readers: ', readers) 58 | -------------------------------------------------------------------------------- /examples/add-callback/README.md: -------------------------------------------------------------------------------- 1 | # Executing functions when card get connected/disconnected 2 | 3 | This example shows you how to add callbacks to the insertion/removal of a smartcard. 4 | 5 | The contents of `get_reader()` and `activate_card(reader)` have already been covered in the previous example [get-card-info](../get-card-info). Please reference that example if something is unclear in these functions. 6 | 7 | Before we add any callbacks we need to first start monitoring the readers for any smartcard insertions / removals. This is done by using the command `observer.start()`: 8 | 9 | cardmonitor, cardobserver = observer.start() 10 | 11 | It is very important to store the `cardmonitor` and `cardobserver` object since these are essential to close the monitoring of the readers: 12 | 13 | observer.stop(cardmonitor, cardobserver) 14 | 15 | Please do not forget to stop the monitoring since this can lead to issues with the reader if left open. 16 | 17 | Using the `add_callback(connect = connected, disconnect = disconnected)` function we are able to add the function `connected(self)` as a callback when a smartcard gets inserted and add the function `disconnected(self)` as a callback when a card gets removed: 18 | 19 | def connected(self): 20 | ... 21 | def disconnected(self): 22 | ... 23 | blocksec2go.add_callback(connect = connected, disconnect = disconnected) 24 | 25 | 26 | Be careful of the fact that the insertion and removal of any smartcard on any reader also triggers the callbacks above. You need to manually check if the inserted or removed card was a Blockchain Security 2Go card. 27 | 28 | Additionally you also need the `reader` object which is used by the other commands in the blocksec2go library: 29 | 30 | def connected(self): 31 | print('A smartcard is connected to the reader!') 32 | reader = get_reader() 33 | activate_card(reader) 34 | 35 | You can press the "Enter" button on your keyboard at any time to stop the monitoring process and close the program. An example output would look like this: 36 | 37 | Insert a Blockchain Security 2Go card onto the reader! 38 | Press Enter at any time to exit the program! 39 | A smartcard is connected to the reader! 40 | Found the specified reader and a Blockchain Security 2Go card! 41 | The card is disconnected from the reader! -------------------------------------------------------------------------------- /examples/get-card-info/README.md: -------------------------------------------------------------------------------- 1 | # Getting basic card information example 2 | 3 | This example shows you how with just a few lines of code you can get the basic card information of your Blockchain Security 2Go card. 4 | 5 | First the main blocksec2go library will be imported, since it provides us with an easy way to talk with the card reader and more importantly with the card itself: 6 | 7 | import blocksec2go 8 | 9 | Before we can actually communicate with the card we need to find a card reader with a smartcard connected to it. This can be done by using the command `find_reader('name of card reader')`. 10 | 11 | If you are unsure what the name of your card reader is, use the command `blocksec2go list_readers` in your cli. Keep in mind that the exact name of the reader can vary across platforms so try keeping the name you enter in the function as simple as possible so it can also be recognised on another platforms too. 12 | 13 | For this example the card reader uTrust 3700 F by the company Identiv was used: 14 | 15 | reader_name = 'Identiv uTrust 3700 F' 16 | ... 17 | reader = blocksec2go.find_reader(reader_name) 18 | 19 | This function returns us the reader as an object to be used with other commands from the blocksec2go library. 20 | 21 | The command `select_app(reader)` has to be used in every application that tries to communicate with the Blockchain Security 2Go card because it activates all the Blockchain commands on the card. It also simultaneously returns us some basic information about the card in form of a tuple: 22 | 23 | pin_active, card_id, version = blocksec2go.select_app(reader) 24 | 25 | The bool `pin_active` that tells us if the card is locked with a PIN code. 26 | The variable `card_id` is a unique card identifier which corresponds to that specific Blockchain Security 2Go card. 27 | The string `version` shows the card firmware version. 28 | 29 | Before you run the example script on your machine make sure to replace the string from `reader_name` with the name of your card reader. Also make sure that everything is connected and the Blockchain Security 2Go card is placed properly on the reader. 30 | 31 | Your output should look something like: 32 | 33 | Found the specified reader and a Blockchain Security 2Go card! 34 | Is PIN enabled? False 35 | Card ID (hex): 02090c2900020027000c 36 | Version: v1.0 37 | -------------------------------------------------------------------------------- /blocksec2go/cli/main.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import logging 3 | import argparse 4 | import json 5 | 6 | from blocksec2go import CardError 7 | 8 | def main(argv=None): 9 | if argv == None: 10 | argv = sys.argv 11 | prog = sys.argv[0] 12 | args = sys.argv[1:] 13 | 14 | parser = argparse.ArgumentParser( 15 | prog=prog, 16 | description='Command line interface for Infineon\'s Blockchain Security 2Go starter kit' 17 | ) 18 | subparsers = parser.add_subparsers(help='subcommands') 19 | parser.add_argument('--reader', help='name of the reader to use') 20 | parser.add_argument('--machine-readable', help='json output', action='store_true') 21 | parser.add_argument( 22 | '--loglevel', 23 | help='log level', 24 | default='info', 25 | choices=['debug', 'info', 'warning', 'error', 'critical', 'nolog'], 26 | ) 27 | 28 | from blocksec2go.cli import (generate_signature, generate_keypair, get_key_info, 29 | list_readers, get_card_info, encrypted_keyimport, set_pin, change_pin, unlock_pin, 30 | disable_pin) 31 | generate_signature.add_subcommand(subparsers) 32 | generate_keypair.add_subcommand(subparsers) 33 | get_key_info.add_subcommand(subparsers) 34 | list_readers.add_subcommand(subparsers) 35 | get_card_info.add_subcommand(subparsers) 36 | encrypted_keyimport.add_subcommand(subparsers) 37 | set_pin.add_subcommand(subparsers) 38 | change_pin.add_subcommand(subparsers) 39 | unlock_pin.add_subcommand(subparsers) 40 | disable_pin.add_subcommand(subparsers) 41 | 42 | args = parser.parse_args(args) 43 | if hasattr(args, 'func'): 44 | if args.loglevel != 'nolog': 45 | logging.basicConfig(level=args.loglevel.upper()) 46 | try: 47 | args.func(args) 48 | return 0 49 | except CardError as e: 50 | if args.machine_readable: 51 | json.dump({'status': 'CardError', 'error': e.response.sw}, fp=sys.stdout) 52 | else: 53 | print(str(e)) 54 | return -1 55 | except Exception as e: 56 | if args.machine_readable: 57 | json.dump({'status': 'error', 'error': str(e)}, fp=sys.stdout) 58 | return -1 59 | else: 60 | raise e 61 | else: 62 | parser.print_help() 63 | return 0 -------------------------------------------------------------------------------- /examples/generate-and-verify-signature/generate_and_verify_signature_example.py: -------------------------------------------------------------------------------- 1 | import blocksec2go 2 | import hashlib 3 | import cryptography.exceptions as crypto_except 4 | 5 | def get_reader(): 6 | reader = None 7 | reader_name = 'Identiv uTrust 3700 F' 8 | while(reader == None): 9 | try: 10 | reader = blocksec2go.find_reader(reader_name) 11 | print('Found the specified reader and a card!', end='\r') 12 | except Exception as details: 13 | if('No reader found' == str(details)): 14 | print('No card reader found! ', end='\r') 15 | elif('No card on reader' == str(details)): 16 | print('Found reader, but no card!', end='\r') 17 | else: 18 | print('ERROR: ' + str(details)) 19 | raise SystemExit 20 | return reader 21 | 22 | def activate_card(reader): 23 | try: 24 | blocksec2go.select_app(reader) 25 | print('Found the specified reader and a Blockchain Security 2Go card!') 26 | except Exception as details: 27 | print('ERROR: ' + str(details)) 28 | raise SystemExit 29 | 30 | def get_public_key(reader, key_id): 31 | try: 32 | if(blocksec2go.is_key_valid(reader, key_id)): 33 | global_counter, counter, key = blocksec2go.get_key_info(reader, key_id) 34 | print('Public key (hex, encoded according to SEC1): ' + key.hex()) 35 | return key 36 | else: 37 | raise RuntimeError('Key_id is invalid!') 38 | except Exception as details: 39 | print('ERROR: ' + str(details)) 40 | raise SystemExit 41 | 42 | if('__main__' == __name__): 43 | reader = get_reader() 44 | activate_card(reader) 45 | 46 | hash_object = hashlib.sha256(b'Hello World!') 47 | hash = hash_object.digest() 48 | print('Hashed message:', hash.hex()) 49 | 50 | key_id = int(input('Which key would you like to use? (Numbers 1 - 255 only!)\n')) 51 | public_key = get_public_key(reader, key_id) 52 | 53 | try: 54 | global_counter, counter, signature = blocksec2go.generate_signature(reader, key_id, hash) 55 | print('Remaining signatures with card: ', global_counter) 56 | print('Remaining signatures with key 1: ', counter) 57 | print('Signature (hex): ' + signature.hex()) 58 | 59 | print('Is signature correct?', blocksec2go.verify_signature(public_key, hash, signature)) 60 | 61 | except crypto_except.InvalidSignature: 62 | print('Verification failed!') 63 | except Exception as details: 64 | print('ERROR: ' + str(details)) 65 | raise SystemExit -------------------------------------------------------------------------------- /examples/get-key-info/README.md: -------------------------------------------------------------------------------- 1 | # Generating new keypairs and getting key information 2 | 3 | This example shows you how to generate new keypairs (public and private keys) and how to recieve important information such as how many signatures the card/key has left or the uncompressed public key corresponding to a specific keyslot. 4 | 5 | The contents of `get_reader()` and `activate_card(reader)` have already been covered in the previous example [get-card-info](../get-card-info). Please reference that example if something is unclear in these functions. 6 | 7 | By default the Blockchain Security 2Go card does not come with any preloaded keys. If this is your first time using the card you have to tell it to generate a keypair using the command `generate_keypair(reader)`: 8 | 9 | key_id = blocksec2go.generate_keypair(reader) 10 | 11 | This function returns the keyslot on which the keypair was generated. 12 | 13 | One of the features of the Blockchain Security 2Go card is that it can store up to 255 keypairs. To get access to a specific keypair you can use the function `get_key_info(reader, key_id)`: 14 | 15 | global_counter, counter, key = blocksec2go.get_key_info(reader, key_id) 16 | 17 | This function returns you a tuple: 18 | The `global_counter` and `counter` variables show how many signatures the card/key have left. 19 | The variable `key` is the Sec1 encoded uncompressed public key of the keypair. 20 | 21 | An example output may look like this: 22 | 23 | Found the specified reader and a Blockchain Security 2Go card 24 | Do you want to create a new Keypair? ("Yes" or "No") 25 | Yes 26 | Keypair generated on slot 1 27 | Get information on which key? (Number between 0-255 only) 28 | 1 29 | Remaining signatures with card: 1000000 30 | Remaining signatures with key 1: 100000 31 | Public key (hex, encoded according to SEC1): 04f70b746ef8c0a6cb23a0ea80c0ccdbb126651299c563cd5896f115c19f1530c32a01ace42842c81142baae62bd142248eadb1bd4fbafbb065c82d5b3c8743990 32 | 33 | ## Warning 34 | Please be sure that the connection between the card and the reader does not get interrupted during the process of generating a new keypair because disturbances during this step can render that keyslot obsolete. If this happens or if a keypair on that slot was not generated yet the command `get_key_info(reader, key_id)` will return 0 remaining signatures on the card/key and the public key will be blank: 35 | 36 | Remaining signatures with card: 0 37 | Remaining signatures with key 1: 0 38 | Public key (hex, encoded according to SEC1): -------------------------------------------------------------------------------- /examples/modify-pin/modify_pin_example.py: -------------------------------------------------------------------------------- 1 | import blocksec2go 2 | 3 | def get_reader(): 4 | reader = None 5 | reader_name = 'Identiv uTrust 3700 F' 6 | while(reader == None): 7 | try: 8 | reader = blocksec2go.find_reader(reader_name) 9 | print('Found the specified reader and a card!', end='\r') 10 | except Exception as details: 11 | if('No reader found' == str(details)): 12 | print('No card reader found! ', end='\r') 13 | elif('No card on reader' == str(details)): 14 | print('Found reader, but no card!', end='\r') 15 | else: 16 | print('ERROR: ' + str(details)) 17 | raise SystemExit 18 | return reader 19 | 20 | def activate_card(reader): 21 | try: 22 | blocksec2go.select_app(reader) 23 | print('Found the specified reader and a Blockchain Security 2Go card!') 24 | except Exception as details: 25 | print('ERROR: ' + str(details)) 26 | raise SystemExit 27 | 28 | if('__main__' == __name__): 29 | reader = get_reader() 30 | activate_card(reader) 31 | mode = input('What would you like to do? ("Set pin", "Change pin", "Unlock pin" or "Verify pin")\n') 32 | try: 33 | if('Set pin' == mode): 34 | pin = input('Please enter a new PIN: ') 35 | puk = blocksec2go.set_pin(reader, pin) 36 | print('PUK to unlock card (hex): ' + puk.hex()) 37 | elif('Change pin' == mode): 38 | old_pin = input('Please enter PIN: ') 39 | new_pin = input('Please enter a new PIN: ') 40 | puk = blocksec2go.change_pin(reader, old_pin, new_pin) 41 | print('New PUK to unlock card (hex): ' + puk.hex()) 42 | elif('Unlock pin' == mode): 43 | puk = input('Please enter PUK: ') 44 | status = blocksec2go.unlock_pin(reader, bytes.fromhex(puk)) 45 | if((True == status) and (isinstance(status, bool))): 46 | print('OK - Unlocked!') 47 | elif(0 != status): 48 | print('ERROR - ' + str(status) + ' tries left') 49 | else: 50 | print('ERROR - Card locked!') 51 | elif('Verify pin' == mode): 52 | pin = input('Please enter PIN: ') 53 | status = blocksec2go.verify_pin(reader, pin) 54 | if((True == status) and (isinstance(status, bool))): 55 | print('OK - Verified!') 56 | elif(0 != status): 57 | print('ERROR - ' + str(status) + ' tries left') 58 | else: 59 | print('ERROR - PIN locked! Please use "Unlock pin" command and PUK to reset PIN.') 60 | else: 61 | raise Exception('Please select one of the 4 commands!') 62 | except Exception as details: 63 | print('ERROR: ' + str(details)) 64 | raise SystemExit -------------------------------------------------------------------------------- /examples/generate-and-verify-signature/README.md: -------------------------------------------------------------------------------- 1 | # Generate and verify a signature 2 | 3 | This examples shows you to generate a signature on the Blockchain Security 2Go card and how to verify a signature using the blocksec2go library. 4 | 5 | The contents of `get_reader()` and `activate_card(reader)` have already been covered in the example [get-card-info](../get-card-info) and the content of `get_public_key(key_id)` in the example [get_key_info](../get-key-info). Please reference those earlier examples if something is unclear in these functions. 6 | 7 | To use the `generate_signature(reader, key_id, hash)` command we first need a hashed message that should be signed using the card. In the example we used "Hello World!" as our message and hashed it using the SHA256 algorithm: 8 | 9 | hash_object = hashlib.sha256(b'Hello World!') 10 | hash = hash_object.digest() 11 | 12 | It is important that we leave the hashed message in bytes since this is the format the card accepts. 13 | 14 | Next, we need a keypair which will be used to sign the hashed message. Please be sure to validate the keypair using the function `is_key_valid(reader, key_id)` since otherwise there is a chance that there is no private key for the signing process. 15 | 16 | After a hashed message and a valid keypair exist we can proceed to actually generate a signature: 17 | 18 | global_counter, counter, signature = blocksec2go.generate_signature(reader, key_id, hash) 19 | 20 | The returned values `global_counter` and `counter` are the same as with the command `get_key_info(reader, key_id)`. The `signature` varibale is the signed hash message in the DER encoded format. For more information on this please check the [Blockchain Security 2Go user manual](https://github.com/Infineon/Blockchain/blob/master/doc/BlockchainSecurity2Go_UserManual.pdf) under paragraph *4.3.2.4 GENERATE SIGNATURE* → *Table 24 ASN.1 DER Signature Encoding Details*. 21 | 22 | The verification of signatures is also possible using the blocksec2go library by using the function `verify_signature(public_key, hash, signature)`. To verify, you will need the hashed message and the public key which correspond to the signed message: 23 | 24 | print('Is signature correct?', blocksec2go.verify_signature(public_key, hash, signature)) 25 | 26 | This command returns a `True` boolean if the signature is correct. If the signature does not correspond to the given public key and hash then a `InvalidSignature` exception will be called. It is important to note that this exception is not part of the blocksec2go library itself, but is passed on by the cryptography library: 27 | 28 | import cryptography.exceptions as crypto_except 29 | ... 30 | except crypto_except.InvalidSignature: 31 | print('Verification failed!') -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Blockchain Security 2Go starter kit Python Library 2 | 3 | This package provides basic functions to communicate with Infineon's Blockchain Security 2Go 4 | starter kit. It abstracts all of the commands available with the starter kit with some simple 5 | functions. 6 | 7 | To get more information about the starter kit go to https://github.com/Infineon/blockchain. 8 | 9 | ## Getting Started 10 | To use this library you need some hardware first: 11 | * A smart card from Infineon's Blockchain Security 2Go starter kit 12 | (see [here](https://www.infineon.com/blockchain) for information about how to get it), and 13 | * a contactless reader to communicate with the contactless smart card. We recommend to use 14 | a reader that is connected via USB (a list is available at 15 | [ccid.apdu.fr](https://ccid.apdu.fr/select_readers/?features=contactless)). 16 | 17 | ### Install Prerequisites 18 | To use the library you need a Python 3 installation (e.g. from http://python.org or via [Anaconda](https://www.anaconda.com/)). 19 | The BlockSec2Go library depends on `pyscard` that requires `swig`. To install `swig` follow the guides at https://github.com/LudovicRousseau/pyscard/blob/master/INSTALL.md or follow the hints below. 20 | 21 | On Windows, we recommend to use the chocolately package manager: 22 | * Install the chocolately package manager 23 | * Open a powershell as administrator mode, run 24 | ``` 25 | $ choco install swig 26 | ``` 27 | On Linux, run 28 | ``` 29 | $ sudo apt-get install swig 30 | ``` 31 | For Mac systems, we recommend to use homebrew 32 | ``` 33 | $ brew install swig 34 | ``` 35 | 36 | ### Install BlockSec2Go 37 | 38 | Then, the fastest way to install the library is to get it via pip 39 | 40 | $ pip3 install blocksec2go 41 | 42 | Remark: When installing Python 3>=3.4 the installer program `pip` is automatically installed (see https://pip.pypa.io/en/stable/installing/). 43 | 44 | This will install the library, which can be imported as `blocksec2go`. 45 | In addition the `blocksec2go` command will be installed which can be used to communicate with 46 | the card from the command line. 47 | 48 | To find out more, run 49 | 50 | $ blocksec2go --help 51 | 52 | The library is tested with Python 3.7.1 and the Identive Cloud 4700 F Dual Interface reader. 53 | 54 | ## Usage Example 55 | ### Command Line Tool 56 | Here is an example of how the command line tool could be used 57 | 58 | $ blocksec2go get_card_info 59 | PIN is: disabled 60 | Card ID (hex): 02058d190004001a002d 61 | Version: v1.0 62 | 63 | $ blocksec2go set_pin 1234 64 | PUK to unlock card (hex): 5c88ce829a2ed32c 65 | 66 | $ blocksec2go generate_keypair 67 | Key ID: 1 68 | 69 | $ blocksec2go get_key_info 1 70 | Remaining signatures with card: 999990 71 | Remaining signatures with key 1: 100000 72 | Public key (hex, encoded according to SEC1): 0434cfd6b1bb53fc244d4881cf1f0d3b9aee7b6ac28aad8a1648fc514101961b59fa7fc58751d0dc876589e467a63ed1582e240cd18b98d408470679418a647833 73 | 74 | $ blocksec2go generate_signature --pin 1234 1 00112233445566778899AABBCCDDEEFF00112233445566778899AABBCCDDEEFF 75 | Remaining signatures with card: 999989 76 | Remaining signatures with key 1: 99999 77 | Signature (hex): 3044022049689b91545ba3bc487af7cb7267d19ea4ad8e2e8b093458e06d46837400444702207fe7cd2b6851049afe0f7c4ced0ef35bd9eb5d044c67ed95045b07a10641806c 78 | 79 | ### Python Library 80 | The command line tool is an abstraction of the Python functions that are delivered by this library. Have a look at the implementation of the commands in [blocksec2go/cli](blocksec2go/cli) to see how the functions are used. 81 | 82 | 83 | 84 | ## Testing 85 | 86 | To develop/test, it's best to use virtualenv. It allows for installing packages 87 | in a "private" environment (for details see https://virtualenv.pypa.io/en/latest/) 88 | (commands intended for Windows in bash, small differences for other OS/shell combinations) 89 | 90 | $ virtualenv venv 91 | $ source ./venv/Scripts/activate 92 | $ pip install --editable . 93 | 94 | You can now test the library as if it would have been installed. 95 | To exit the environment, simply run 96 | 97 | $ deactivate 98 | -------------------------------------------------------------------------------- /examples/modify-pin/README.md: -------------------------------------------------------------------------------- 1 | # Setting and modifying PIN values 2 | 3 | This example shows you the functionality and usage of the following four commands: 4 | * [set_pin](#set_pin-command) - Sets a new PIN value 5 | * [change_pin](#change_pin-command) - Changes current PIN value to a new one 6 | * [unlock_pin](#unlock_pin-command) - Disables PIN using the corresponding PUK 7 | * [verify_pin](#verify_pin-command) - Verifies a PIN and enables usage of certain command 8 | 9 | The contents of `get_reader()` and `activate_card(reader)` have already been covered in the example [get-card-info](../get-card-info). Please reference the previous example if something is unclear in these functions. 10 | 11 | ## set_pin command 12 | The `set_pin(reader, pin)` command is used to set a new PIN on the Blockchain Security 2Go card. The PIN value uses UTF-8 encoding and has to have a minimum length of 4 but can not exceed a maximum length of 62 bytes. Example PIN values may look like: `1234`, `abcd`, `1234abcd`, `Even this sentence can work as a PIN!`. Like every PIN you should try to make it secure and unpredictable. After using the command it is highly recommended to always store the returned PUK. This PUK has to be used in case the PIN gets locked (PIN entered incorrectly 3 times). Running the example and selecting the "Set pin" instruction will result in an output similar to: 13 | 14 | Found the specified reader and a Blockchain Security 2Go card! 15 | What would you like to do? ("Set pin", "Change pin", "Unlock pin" or "Verify pin") 16 | Set pin 17 | Please enter a new PIN: 1234 18 | PUK to unlock card (hex): dd401ad08a1f0dcd 19 | 20 | Note that if the card already has a PIN, then executing this line will return an error. 21 | 22 | ## change_pin command 23 | The `change_pin(reader, old_pin, new_pin)` command is used to change the current PIN value on the Blockchain Security 2Go card to a new PIN value. Same as with the [set_pin](#set_pin-command) command it is very recommended to store the new PUK which was returned. Running the example and selecting the "Change pin" instruction will result in an output similar to: 24 | 25 | Found the specified reader and a Blockchain Security 2Go card! 26 | What would you like to do? ("Set pin", "Change pin", "Unlock pin" or "Verify pin") 27 | Change pin 28 | Please enter PIN: 1234 29 | Please enter a new PIN: abcd 30 | New PUK to unlock card (hex): 029ed4787d648666 31 | 32 | This command will not work if the PIN has been entered incorrectly 3 times! If you have locked your PIN then you need to use the command [unlock_pin](#unlock_pin-command) and the PUK to reset the PIN. 33 | 34 | ## unlock_pin command 35 | The `unlock_pin(reader, bytes.fromhex(puk))` command is used to deactivate the PIN authentication from the Blockchain Security 2Go card using the PUK. It can also be used in case the PIN is locked and you have to reset the PIN value. After using this command there will be no PIN on the card. Using this function returns a boolean which is `True` if the PUK was correct. If it was not correct it will return an integer with the remaining tries left. Running the example and selecting the "Unlock pin" instruction will result in an output that should look like: 36 | 37 | Found the specified reader and a Blockchain Security 2Go card! 38 | What would you like to do? ("Set pin", "Change pin", "Unlock pin" or "Verify pin") 39 | Unlock pin 40 | Please enter PUK: dd401ad08a1f0dcd 41 | OK - Unlocked! 42 | 43 | Keep in mind that having no PIN is less secure than using a PIN and therefore it is strongly recommended that you always have a PIN enabled. 44 | 45 | Under certain circumstances, for example if you lost the PUK but the PIN is still known, then the functions [change_pin](#change_pin-command) and [unlock_pin](#unlock_pin-command) can be used together to reset the PIN: 46 | 47 | temp_pin = "1234" 48 | temp_puk = blocksec2go.change_pin(reader, known_pin, temp_pin) 49 | blocksec2go.unlock_pin(reader, bytes.fromhex(temp_puk)) 50 | 51 | The value of `temp_pin` is completely irrelevant since it will in any case be removed in the next line. 52 | 53 | ## verify_pin command 54 | The `verify_pin(reader, pin)` command is used to check the PIN value and it simultaneously enables the usage of the commands `encrypted_keyimport` and `generate_signature`. To get more information on the latter, please refer to the example [generate-signature](../generate-signature). Using the function `verify_pin(reader, pin)` returns a boolean which is `True` if the PIN was correct. If it was not correct it will return an integer with the remaining tries left. Running the example and selecting the "Verify pin" instruction will result in an output that should look like: 55 | 56 | Found the specified reader and a Blockchain Security 2Go card! 57 | What would you like to do? ("Set pin", "Change pin", "Unlock pin" or "Verify pin") 58 | Verify pin 59 | Please enter PIN: 1234 60 | OK - Verified! -------------------------------------------------------------------------------- /blocksec2go/comm/base.py: -------------------------------------------------------------------------------- 1 | _ERRORS = { 2 | 0x6700: 'Invalid length', 3 | 0x6983: 'Authentication failed - Locked', 4 | 0x6985: 'Condition of use not satisfied - no active PIN session/invalid PIN state for command', 5 | 0x6A80: 'Invalid serialization of data', 6 | 0x6A82: 'Security status not satisfied - counter exceeded', 7 | 0x6A84: 'Not enough memory - key storage full', 8 | 0x6A88: 'Referenced data not found - key with the given index is not available', 9 | 0x6A86: 'Incorrect parameters P1/P2', 10 | 0x6A87: 'Lc inconsistent', 11 | 0x6D00: 'INS is not supported (App may not be selected)', 12 | 0x6E00: 'CLA is not supported', 13 | 0x6F00: 'Unknown error', 14 | 0x9000: 'Success', 15 | } 16 | 17 | class CardError(Exception): 18 | """ Exception if card indicates failure 19 | 20 | Args: 21 | message (str): exception message 22 | explanation (str, optional): possible error explanation 23 | ``None`` to set according to response status word 24 | response (:obj:`ApduResponse`, optional): raw response 25 | received from card 26 | 27 | Attributes: 28 | message (str): exception message 29 | explanation (str): error explanation if available 30 | response (int): returned status word of card 31 | """ 32 | def __init__(self, message, response, explanation=None): 33 | super(CardError, self).__init__(message) 34 | self.message = message 35 | self.explanation = explanation 36 | self.response = response 37 | 38 | def __str__(self): 39 | string = self.message 40 | if self.explanation is not None: 41 | string += str(self.explanation) 42 | if self.response is not None: 43 | if self.explanation is not None: 44 | string += ' (' 45 | string += str(self.response) 46 | if self.explanation is not None: 47 | string += ')' 48 | return string 49 | 50 | def __repr__(self): 51 | return 'CardError(' + self.message + ', ' + self.explanation + ', ' + repr(self.response) + ')' 52 | 53 | 54 | class ApduResponse: 55 | """ Cards response to command 56 | 57 | Args: 58 | resp (bytes): data portion of response 59 | sw (int): status word 60 | 61 | Attributes: 62 | resp (bytes): data portion of response 63 | sw (int): status word 64 | """ 65 | def __init__(self, resp, sw): 66 | self.resp = resp 67 | self.sw = sw 68 | 69 | def __bool__(self): 70 | return self.sw == 0x9000 71 | 72 | def check(self): 73 | if self.sw != 0x9000: 74 | raise CardError('Card indicated failure: ', response=self) 75 | return self 76 | 77 | def __str__(self): 78 | text = "" 79 | if (self.sw & 0xFFF0) == 0x63C0: 80 | text = 'Authentication failed - {} tries remaining'.format(self.sw & 0xF) 81 | elif (self.sw & 0xFF00) == 0x6400: 82 | text = 'Operation failed - fatal error {}'.format(self.sw & 0xFF) 83 | elif self.sw in _ERRORS: 84 | text = _ERRORS[self.sw] 85 | 86 | return hex(self.sw) + ' ' + text 87 | 88 | def __repr__(self): 89 | return 'ApduResponse(' + repr(self.resp) + ', ' + repr(self.sw) + ')' 90 | 91 | 92 | class Apdu: 93 | """ Command to send to card 94 | 95 | According to ISO7816-3 96 | 97 | Args: 98 | header (bytes): CLA, INS, P1, P2 bytes 99 | data (bytes, optional): data field of APDU 100 | le (int): expected response length 101 | ``None`` for no response 102 | ``-1`` for maximum size dependent on short/extended APDU 103 | """ 104 | def __init__(self, header, data = None, le = None): 105 | if len(header) != 4: 106 | raise ValueError('invalid header length: ' + str(header) + ' (' + str(len(header)) + ')') 107 | if len(data) > 255 or le > 256: 108 | raise RuntimeError('extended length APDU support not yet implemented') 109 | 110 | self.header = header 111 | self.data = data 112 | self.le = le 113 | 114 | def __bytes__(self): 115 | """ Encode APDU as bytes 116 | """ 117 | apdu = b'' 118 | apdu += self.header 119 | if len(self.data) > 0: 120 | apdu += bytes([len(self.data)]) 121 | apdu += self.data 122 | 123 | if self.le < 0: 124 | apdu += b'\x00' 125 | if self.le > 0: 126 | apdu += bytes([self.le]) 127 | 128 | return apdu 129 | 130 | def __str__(self): 131 | leByte = None 132 | if self.le < 0: 133 | leByte = '00' 134 | if self.le > 0: 135 | leByte = bytes([self.le]).hex() 136 | 137 | s = 'apdu ' + self.header.hex() 138 | if self.data is not None: 139 | s += ' ' + self.data.hex() 140 | if leByte is not None: 141 | s+= ' ' + leByte 142 | return s 143 | 144 | def __repr__(self): 145 | return 'Apdu(' + repr(self.header) + ', ' + repr(self.data) + ', ' + repr(self.le) + ')' -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | First off, thanks for taking the time to contribute! We are really glad you're reading this, because we need volunteer developers to help this project come to fruition. 3 | 4 | When contributing to this repository, please first discuss the change you wish to make via issue. 5 | 6 | Please note we have a code of conduct, please follow it in all your interactions with the project. 7 | 8 | ## I don't want to read this whole thing I just have a question!! 9 | File an issue to ask a question. 10 | 11 | ## How Can I Contribute? 12 | ### Report a Bug or Problem 13 | Did you find a bug? 14 | * Ensure that the bug was not already reported by searching it under [issues](https://github.com/Infineon/BlockchainSecurity2Go-Python-Library/Issues) 15 | * If you do not find an open issue, [open a new one](https://github.com/Infineon/BlockchainSecurity2Go-Python-Library/issues/new). Be sure to include a *title and clear description* and as much relevant information as possible including a *code sample* or an *executable test case* that demonstrate the problem. 16 | 17 | Did you write a patch that fixes a bug? 18 | * Open a new GitHub pull request with the patch 19 | * Ensure that the description of the pull request describes the problem and the solution. If applicatble, include the relevant issue number. 20 | 21 | ### Suggest Enhancements 22 | * Open a new GitHub pull request with the new feature. 23 | * Ensure that the description of the pull request includes a clear description of the enhancement. 24 | 25 | ### Pull Request 26 | Always write a clear log message for your commits. One-line messages are fine for small changes, but bigger changes should look like this: 27 | 28 | $ git commit -m "A brief summary of the commit 29 | > 30 | > A paragraph describing what changed and its impact." 31 | 32 | 33 | Always double check that the pull request dos not crash the build of the project 34 | 35 | ## Code of Conduct 36 | 37 | ### Our Pledge 38 | 39 | In the interest of fostering an open and welcoming environment, we as 40 | contributors and maintainers pledge to making participation in our project and 41 | our community a harassment-free experience for everyone, regardless of age, body 42 | size, disability, ethnicity, gender identity and expression, level of experience, 43 | nationality, personal appearance, race, religion, or sexual identity and 44 | orientation. 45 | 46 | ### Our Standards 47 | 48 | Examples of behavior that contributes to creating a positive environment 49 | include: 50 | 51 | * Using welcoming and inclusive language 52 | * Being respectful of differing viewpoints and experiences 53 | * Gracefully accepting constructive criticism 54 | * Focusing on what is best for the community 55 | * Showing empathy towards other community members 56 | 57 | Examples of unacceptable behavior by participants include: 58 | 59 | * The use of sexualized language or imagery and unwelcome sexual attention or 60 | advances 61 | * Trolling, insulting/derogatory comments, and personal or political attacks 62 | * Public or private harassment 63 | * Publishing others' private information, such as a physical or electronic 64 | address, without explicit permission 65 | * Other conduct which could reasonably be considered inappropriate in a 66 | professional setting 67 | 68 | ### Our Responsibilities 69 | 70 | Project maintainers are responsible for clarifying the standards of acceptable 71 | behavior and are expected to take appropriate and fair corrective action in 72 | response to any instances of unacceptable behavior. 73 | 74 | Project maintainers have the right and responsibility to remove, edit, or 75 | reject comments, commits, code, wiki edits, issues, and other contributions 76 | that are not aligned to this Code of Conduct, or to ban temporarily or 77 | permanently any contributor for other behaviors that they deem inappropriate, 78 | threatening, offensive, or harmful. 79 | 80 | ### Scope 81 | 82 | This Code of Conduct applies both within project spaces and in public spaces 83 | when an individual is representing the project or its community. Examples of 84 | representing a project or community include using an official project e-mail 85 | address, posting via an official social media account, or acting as an appointed 86 | representative at an online or offline event. Representation of a project may be 87 | further defined and clarified by project maintainers. 88 | 89 | ### Enforcement 90 | 91 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 92 | reported by contacting the maintenance team. 93 | 94 | Project maintainers who do not follow or enforce the Code of Conduct in good 95 | faith may face temporary or permanent repercussions as determined by other 96 | members of the project's leadership. 97 | 98 | ### Attribution 99 | 100 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 101 | available at [http://contributor-covenant.org/version/1/4][version] as well as from the [OpenGovernment Project](https://github.com/opengovernment/opengovernment/edit/master/CONTRIBUTING.md) and the [Atom Project](https://github.com/atom/atom). 102 | 103 | [homepage]: http://contributor-covenant.org 104 | [version]: http://contributor-covenant.org/version/1/4/ 105 | -------------------------------------------------------------------------------- /blocksec2go/commands.py: -------------------------------------------------------------------------------- 1 | import logging 2 | logger = logging.getLogger(__name__) 3 | 4 | from smartcard.System import readers 5 | from blocksec2go.comm.card_observer import card_observer 6 | from blocksec2go.comm.pyscard import open_pyscard 7 | 8 | from cryptography.hazmat.primitives.asymmetric import ec 9 | from cryptography.hazmat.primitives.asymmetric import utils 10 | from cryptography.hazmat.primitives import hashes 11 | 12 | def add_callback(connect, disconnect): 13 | """ Add callbacks 14 | 15 | Adds function callbacks to ``CardObserver`` class 16 | 17 | Args: 18 | connect (func): Function to call when card gets connected 19 | disconnect (func): Function to call when card gets disconnected 20 | 21 | Returns: 22 | Raises: 23 | """ 24 | card_observer.connect = connect 25 | card_observer.disconnect = disconnect 26 | 27 | def find_reader(reader_name): 28 | """ Looks for a specific card reader 29 | 30 | Tries to find a card reader with specified name. 31 | 32 | Args: 33 | reader_name (str): string providing name of reader 34 | 35 | Returns: 36 | reader: 37 | :obj:`PyScardReader`: PyScard wrapper object. 38 | Chooses first reader with specified name if multiple 39 | readers are found. 40 | 41 | Raises: 42 | RuntimeError: No reader found with specified name. 43 | 44 | Any exceptions thrown by the reader wrapper are passed through. 45 | """ 46 | logger.debug('FIND READER name %s', reader_name) 47 | r = readers() 48 | for reader in r: 49 | if reader_name in str(reader): 50 | try: 51 | return open_pyscard(r[r.index(reader)]) 52 | except: 53 | raise RuntimeError('No card on reader') 54 | raise RuntimeError('No reader found') 55 | 56 | def select_app(reader): 57 | """ Sends command to select the Blockchain Security2GO application 58 | 59 | Needs to be called after reset to allow for access to 60 | blockchain commands. 61 | 62 | Returns: 63 | :obj:`tuple`: (pin_active, card_id, version). 64 | 65 | pin_active: 66 | bool: True if PIN is set on the card 67 | 68 | card_id: 69 | bytes: 10 byte unique card identifier 70 | 71 | version: 72 | str: card firmware version, following 73 | semantic versioning. 74 | 75 | Raises: 76 | CardError: If card indicates a failure. 77 | 78 | Any exceptions thrown by the reader wrapper are passed through. 79 | """ 80 | logger.debug('SELECT Blockchain Security 2Go starter kit') 81 | aid = bytes.fromhex('D2760000041502000100000001') 82 | r = reader.transceive(b'\x00\xA4\x04\x00', aid).check() 83 | 84 | pin_active = True if r.resp[0] == 1 else False 85 | card_id = r.resp[1:11] 86 | version = r.resp[11:].decode('ASCII') 87 | return (pin_active, card_id, version) 88 | 89 | def generate_keypair(reader): 90 | """ Sends command to generate new keypair 91 | 92 | A new keypair is generated and stored. The ID identifying this 93 | keypair is returned. A key using the `secp256k1`_ curve is generated. 94 | 95 | Args: 96 | reader (:obj:): object providing reader communication 97 | 98 | Returns: 99 | int: ID of the just generated keypair, to be used e.g. for 100 | future signatures using ``generate_signature`` 101 | 102 | Raises: 103 | CardError: If card indicates a failure, e.g. if card is full. 104 | 105 | Any exceptions thrown by the reader wrapper are passed through. 106 | 107 | .. _secp256k1: 108 | http://www.secg.org/sec2-v2.pdf 109 | """ 110 | logger.debug('GENERATE KEYPAIR') 111 | r = reader.transceive(b'\x00\x02\x00\x00').check() 112 | 113 | key_id = int(r.resp[0]) 114 | logger.debug('generated key %d', key_id) 115 | return key_id 116 | 117 | def get_key_info(reader, key_id): 118 | """ Sends command to retrieve keypair information 119 | 120 | Args: 121 | reader (:obj:): object providing reader communication 122 | key_id (int): key ID as returned by ``generate_keypair`` 123 | 124 | Returns: 125 | :obj:`tuple`: (global_counter, counter, key) 126 | 127 | global_counter: 128 | int: overall remaining signatures for this card 129 | 130 | counter: 131 | int: signatures remaining with key ``key_id`` 132 | 133 | key: 134 | bytes: public key, encoded uncompressed as 135 | point according to `SEC1`_ 136 | 137 | Uncompressed SEC1 encoding in short means that the key is 138 | encoded to a 65 byte string. It consists of a 1 byte prefix 139 | followed by the coordinates (first x then y) with a constant 140 | length of 32 byte each. 141 | The prefix is always 0x04, both coordinates are encoded as 142 | unsigned integers, MSB first (big endian). 143 | 144 | Raises: 145 | CardError: If card indicates a failure, e.g. if ID is invalid. 146 | 147 | Any exceptions thrown by the reader wrapper are passed through. 148 | 149 | .. _SEC1: 150 | http://www.secg.org/sec1-v2.pdf 151 | """ 152 | logger.debug('GET KEY INFO key %d', key_id) 153 | if key_id < 0 or key_id > 255: 154 | raise RuntimeError('Invalid key_id: ' + str(key_id)) 155 | 156 | header = '0016{:02x}00'.format(key_id) 157 | r = reader.transceive(bytes.fromhex(header)) 158 | 159 | global_counter = int.from_bytes(r.resp[0:4], byteorder='big') 160 | counter = int.from_bytes(r.resp[4:8], byteorder='big') 161 | key = r.resp[8:] 162 | logger.debug('global count %d, count %d, public key %s', global_counter, counter, key.hex()) 163 | return (global_counter, counter, key) 164 | 165 | def is_key_valid(reader, key_id): 166 | """ Validate retrieved keypair information 167 | 168 | Args: 169 | reader (:obj:): object providing reader communication 170 | key_id (int): key ID as returned by ``generate_keypair`` 171 | 172 | Returns: 173 | key_valid: 174 | bool: True if key is valid, else false 175 | 176 | Raises: 177 | RuntimeError: Entered key_id is outside of keypair scope. 178 | 179 | Any exceptions thrown by the reader wrapper are passed through. 180 | """ 181 | logger.debug('IS KEY VALID key %d', key_id) 182 | if key_id < 0 or key_id > 255: 183 | raise RuntimeError('Invalid key_id: ' + str(key_id)) 184 | global_counter, counter, key = get_key_info(reader, key_id) 185 | if(0 == global_counter): return False 186 | elif(0 == counter): return False 187 | else: return True 188 | 189 | def generate_signature(reader, key_id, hash): 190 | """ Send command to calculate signature 191 | 192 | Signs a given hash using the specified key. The signature is 193 | done using the sec256k1 curve, and DER encoded. 194 | The returned signature is canonical, as described in `BIP 62`_. 195 | Hashing needs to be done on the PC/terminal side, the card expects 196 | already hashed data. 197 | 198 | If a PIN is enabled on the card, a PIN session must be in 199 | progress to use ``encrypted_keyimport``. See ``verify_pin`` 200 | for more information. 201 | 202 | Args: 203 | reader (:obj:): object providing reader communication 204 | key_id (int): key ID as returned by ``generate_keypair`` 205 | hash (bytes): 32 byte long hash to sign 206 | 207 | Returns: 208 | :obj:`tuple`: (global_counter, counter, signature) 209 | 210 | global_counter: 211 | int: overall remaining signatures for this card 212 | 213 | counter: 214 | int: signatures remaining with key ``key_id`` 215 | 216 | signature: 217 | bytes: DER encoded signature 218 | 219 | Raises: 220 | CardError: If card indicates a failure, e.g. if ID is invalid. 221 | 222 | Any exceptions thrown by the reader wrapper are passed through. 223 | 224 | .. _BIP 62: 225 | https://github.com/bitcoin/bips/blob/master/bip-0062.mediawiki 226 | """ 227 | logger.debug('GENERATE SIGNATURE key %d hash %s', key_id, hash.hex()) 228 | if key_id < 0 or key_id > 255: 229 | raise RuntimeError('Invalid key_id: ' + str(key_id)) 230 | if len(hash) != 32: 231 | raise RuntimeError('Invalid hash length') 232 | 233 | header = '0018{:02x}00'.format(key_id) 234 | r = reader.transceive(bytes.fromhex(header), hash).check() 235 | 236 | global_counter = int.from_bytes(r.resp[0:4], byteorder='big') 237 | counter = int.from_bytes(r.resp[4:8], byteorder='big') 238 | signature = r.resp[8:] 239 | logger.debug('global count %d, count %d, signature %s', global_counter, counter, signature.hex()) 240 | return (global_counter, counter, signature) 241 | 242 | def verify_signature(key, hash, signature): 243 | """ Verification command to check signature 244 | 245 | Verifies a signature which is returned by ``generate_signature`` 246 | using a public key and the hashed message. The returned value 247 | is a True boolean if the signature is verified correctly, but 248 | returns a InvalidSignature exception if incorrect. 249 | 250 | Args: 251 | key (bytes): `SEC1`_ encoded uncompressed public key 252 | hash (bytes): 32 byte long hash which was signed 253 | signature (bytes): DER encoded signature 254 | 255 | Returns: 256 | verification: 257 | boolean: True if signature gets verified correctly 258 | 259 | Raises: 260 | Any exceptions thrown by the cryptography wrapper are passed through. 261 | """ 262 | logger.debug('VERIFY SIGNATURE public key %s, hash %s, signature %s', key.hex(), hash.hex(), signature.hex()) 263 | 264 | public_key = ec.EllipticCurvePublicKey.from_encoded_point(ec.SECP256K1(), key) 265 | return public_key.verify(signature, hash, ec.ECDSA(utils.Prehashed(hashes.SHA256()))) == None 266 | 267 | def encrypted_keyimport(reader, seed): 268 | """ Sends command to derive key from given seed 269 | 270 | The card will reproducibly generate a key from the 271 | given seed. This allows the user to backup the seed 272 | and provides a fallback in case of running our of 273 | signatures or destruction of the card. 274 | The key is generated using key derivation as defined 275 | in `NIST SP 800-108`_ using CMAC-AES256 as defined in 276 | `NIST SP 800-38B`_. 277 | 278 | If a PIN is enabled on the card, a PIN session must be in 279 | progress to use ``encrypted_keyimport``. See ``verify_pin`` 280 | for more information. 281 | 282 | Args: 283 | reader (:obj:): object providing reader communication 284 | seed (bytes): 16 byte seed to use for key generation 285 | 286 | Raises: 287 | CardError: If card indicates a failure, e.g. for invalid seed length. 288 | 289 | Any exceptions thrown by the reader wrapper are passed through. 290 | 291 | .. _NIST SP 800-108: 292 | https://csrc.nist.gov/publications/detail/sp/800-108/final 293 | .. _NIST SP 800-38B: 294 | https://csrc.nist.gov/publications/detail/sp/800-38b/final 295 | """ 296 | logger.debug('GENERATE KEY FROM SEED seed %s', seed.hex()) 297 | if len(seed) != 16: 298 | raise RuntimeError('Invalid seed length') 299 | 300 | reader.transceive(b'\x00\x20\x00\x00', seed).check() 301 | logger.debug('success') 302 | 303 | def set_pin(reader, pin): 304 | """ Send command to set a PIN 305 | 306 | Sets a PIN as long as there is no PIN enabled currently. 307 | Returns the PUK that is needed in the case of lockout 308 | because of too many incorrect PIN entries. 309 | 310 | Args: 311 | reader (:obj:): object providing reader communication 312 | pin (str): PIN to be used, will be used UTF-8 encoded 313 | 314 | Returns: 315 | bytes: PUK value needed for unlock 316 | 317 | Raises: 318 | CardError: If card indicates a failure, e.g. if there is alredy a PIN set. 319 | 320 | Any exceptions thrown by the reader wrapper are passed through. 321 | """ 322 | logger.debug('SET PIN pin %s', pin) 323 | r = reader.transceive(b'\x00\x40\x00\x00', pin.encode()).check() 324 | 325 | logger.debug('new puk %s', r.resp.hex()) 326 | return r.resp 327 | 328 | def change_pin(reader, current_pin, new_pin): 329 | """ Send command to modify existing PIN 330 | 331 | Changes the PIN if a PIN is currently enabled. 332 | Returns a new PUK that is needed in the case of lockout 333 | because of too many incorrect PIN entries. 334 | 335 | Args: 336 | reader (:obj:): object providing reader communication 337 | current_pin (str): current PIN, will be used UTF-8 encoded 338 | new_pin (str): new PIN to set, will be used UTF-8 encoded 339 | 340 | Returns: 341 | bytes: PUK value needed for unlock 342 | 343 | Raises: 344 | CardError: If card indicates a failure, e.g. if too many incorrect 345 | PUK entry tries alrady occured and card is locked permanently. 346 | 347 | Any exceptions thrown by the reader wrapper are passed through. 348 | """ 349 | logger.debug('CHANGE PIN from %s to %s', current_pin, new_pin) 350 | if len(current_pin.encode()) > 255: 351 | raise RuntimeError('Invalid length for current PIN') 352 | if len(new_pin.encode()) > 255: 353 | raise RuntimeError('Invalid length for new PIN') 354 | 355 | data = bytes([len(current_pin)]) + current_pin.encode() 356 | data += bytes([len(new_pin)]) + new_pin.encode() 357 | 358 | r = reader.transceive(b'\x00\x42\x00\x00', data).check() 359 | logger.debug('new puk %s', r.resp.hex()) 360 | return r.resp 361 | 362 | def verify_pin(reader, pin): 363 | """ Sends command to verify PIN and unlock commands 364 | 365 | If the provided PIN is correct, this starts a PIN 366 | session. An ongoing PIN session allows to use protected 367 | commands until the next reset/select command (``select_app``). 368 | 369 | Args: 370 | reader (:obj:): object providing reader communication 371 | pin (str) 372 | 373 | Returns: 374 | 375 | Raises: 376 | CardError: If card indicates a failure, e.g. if too many incorrect 377 | PIN entry tries alrady occured and card is locked. 378 | 379 | Any exceptions thrown by the reader wrapper are passed through. 380 | """ 381 | # TODO fix interface, do not return bool or int depening on success/failure 382 | logger.debug('VERIFY PIN pin %s', pin) 383 | r = reader.transceive(b'\x00\x44\x00\x00', pin.encode()) 384 | 385 | if r.sw == 0x9000: 386 | logger.debug('success') 387 | return True 388 | if r.sw == 0x6983: 389 | logger.debug('failed - PIN locked') 390 | return 0 391 | if (r.sw & 0xFFF0) == 0x63C0: 392 | logger.debug('failed, %d tries remaining', r.sw & 0xF) 393 | return r.sw & 0xF 394 | r.check() 395 | 396 | def unlock_pin(reader, puk): 397 | """ Send command to unlock PIN using PUK 398 | 399 | If too many incorrect PIN entries occured and the card is locked 400 | it can be unlocked using the PUK returned while setting the PIN. 401 | 402 | Args: 403 | reader (:obj:): object providing reader communication 404 | pin (bytes): as returned from ``set_pin`` or ``change_pin`` 405 | 406 | Returns: 407 | ... 408 | 409 | Raises: 410 | CardError: If card indicates a failure, e.g. if too many incorrect 411 | PIN entry tries alrady occured and card is locked. 412 | 413 | Any exceptions thrown by the reader wrapper are passed through. 414 | """ 415 | # TODO fix interface, do not return bool or int depening on success/failure 416 | logger.debug('UNLOCK PIN puk %s', puk.hex()) 417 | r = reader.transceive(b'\x00\x46\x00\x00', puk) 418 | 419 | if r.sw == 0x9000: 420 | logger.debug('success') 421 | return True 422 | if r.sw == 0x6983: 423 | logger.debug('failed - card locked') 424 | return 0 425 | if (r.sw & 0xFFF0) == 0x63C0: 426 | logger.debug('failed, %d tries remaining', r.sw & 0xF) 427 | return r.sw & 0xF 428 | r.check() 429 | --------------------------------------------------------------------------------