#!/usr/bin/env python3

# Copyright 2023 Sergey Stolyarov <sergei@regolit.com>
#
# Distributed under New BSD License.
#
# https://opensource.org/license/bsd-3-clause/


from time import sleep
from argparse import ArgumentParser
from smartcard.System import readers
from smartcard.CardRequest import CardRequest
from smartcard.CardConnection import CardConnection
from smartcard.util import toHexString, toBytes

READ_TIMEOUT = 0.1

def main() -> int:
	parser = ArgumentParser()
	parser.add_argument('--verbose', action='store_true')
	args = parser.parse_args()

	reader = readers()[0]
	print('Connected reader: {0}'.format(reader))
	cardrequest = CardRequest(timeout=None, readers=[reader])
	print('Waiting for Mifare Classic 1K/4K card...')

	cardservice = cardrequest.waitforcard()
	cardservice.connection.connect()

	print('Card connected, reading data, please wait...')

	test_keys = [
		"A0 B0 C0 D0 E0 F0",
		"FF FF FF FF FF FF",
		"00 00 00 00 00 00",
		"D3 F7 D3 F7 D3 F7",
		"A0 A1 A2 A3 A4 A5",
		"B0 B1 B2 B3 B4 B5",

		"F1 F2 F3 F4 F5 F6"
	]

	data = []  # blocks data, each element is a list of sector blocks
	found_keys = []  # each element is a tuple (Key A, Key B), either could be None
	acs = []  # access conditions for all sectors, each element is a list with AC for every block

	# try to access first 16 sectors
	for sector in range(16):
		sector_data = [[],[],[],[]]
		blocks_access_condition_bits = None
		key_a = None
		key_b = None

		for key in test_keys:
			# store key data to the first cell (CellN = P2 = 0)
			#       CLA INS P1  P2  Lc
			apdu = "FF  82  00  00  06 " + key
			response, sw1, sw2 = cardservice.connection.transmit(toBytes(apdu))
			if (sw1,sw2) != (0x90,0x00):
				print('Load Keys command failed, Key "{}" , probably not Mifare compatible card, terminating.'.format(key))
				return 1

			# perform authentication for 4th (trailer) block of the sector using stored data as Key A
			blockMSB = 0
			blockLSB = sector * 4 + 3  # 4th block
			#       CLA INS P1  P2  Lc  VER BlockMSB BlockLSB KeyA CellN
			apdu = "FF  86  00  00  05  01  {:02X}   {:02X}   60   00".format(blockMSB, blockLSB)
			response, sw1, sw2 = cardservice.connection.transmit(toBytes(apdu))
			if (sw1,sw2) == (0x90,0x00):
				if args.verbose: print('Authentication successful with Key A "{}" to block {:02X}, sector {:02X}'.format(key, blockLSB, sector))
				key_a = key
				# try to read trailer block content
				#       CLA INS BlockMSB BlockLSB Le
				apdu = 'FF  B0  {:02X}   {:02X}   10'.format(blockMSB, blockLSB)
				response, sw1, sw2 = cardservice.connection.transmit(toBytes(apdu))
				if (sw1,sw2) != (0x90,0x00):
					if args.verbose: print('Failed to read trailer of sector {:02X}, SW: "{}"'.format(sector, toHexString([sw1,sw2])))
				else:
					sector_data[3] = response
					access_conditions_bytes = response[6:9]
					blocks_access_condition_bits = unpack_access_conditions_bits(access_conditions_bytes)

					if can_read_key_b_bytes(blocks_access_condition_bits[3]):
						key_b = toHexString(response[10:])  # last 6 bytes is Key B

					# try to read first three blocks of the sector if access conditions allow
					for sector_block in range(3):
						if can_read_block_with_key_a(blocks_access_condition_bits[sector_block]):
							blockMSB = 0
							blockLSB = sector * 4 + sector_block
							#       CLA INS BlockMSB BlockLSB Le
							apdu = 'FF  B0  {:02X}   {:02X}   10'.format(blockMSB, blockLSB)
							response, sw1, sw2 = cardservice.connection.transmit(toBytes(apdu))
							if (sw1,sw2) != (0x90,0x00):
								if args.verbose: print('Failed to read block {:02X} with Key A, SW: {}'.format(blockLSB, toHexString([sw1,sw2])))
							else:
								sector_data[sector_block] = response
				break

		if key_b == None:
			# try to find Key B
			for key in test_keys:
				# store key data to cell 0
				apdu = "FF 82 00 00 06 " + key
				response, sw1, sw2 = cardservice.connection.transmit(toBytes(apdu))
				if (sw1,sw2) != (0x90,0x00):
					print('Load Keys command failed, Key "{}" , probably not Mifare compatible card, terminating.'.format(key))
					return 1

				# perform authentication for 4th (trailer) block of the sector using stored data as Key B
				blockMSB = 0
				blockLSB = sector * 4 + 3  # 4th block
				#       CLA INS P1 P2 Lc  VER BlockMSB BlockLSB KeyA CellN
				apdu = "FF  86  00 00 05  01  {:02X}   {:02X}   61   00".format(blockMSB, blockLSB)
				response, sw1, sw2 = cardservice.connection.transmit(toBytes(apdu))
				if (sw1,sw2) == (0x90,0x00):
					if args.verbose: print('Authentication successful with Key B "{}" to block {:02X}, sector {:02X}'.format(key, blockLSB, sector))
					key_b = key
					# try to read each sector block if it's not read already
					auth_discarded = False
					for sector_block in reversed(range(4)):
						if sector_data[sector_block] != []:
							continue

						blockMSB = 0
						blockLSB = sector * 4 + sector_block

						if auth_discarded:
							# authenticate again, it's required because Mifare chip discards previous
							# authentication after any error
							#       CLA INS P1 P2 Lc  VER BlockMSB BlockLSB KeyA CellN
							apdu = "FF  86  00 00 05  01  {:02X}   {:02X}   61   00".format(blockMSB, blockLSB)
							response, sw1, sw2 = cardservice.connection.transmit(toBytes(apdu))
							# ignore errors because ACR122U returns 63 00 here for some reason 

						#       CLA INS BlockMSB BlockLSB Le
						apdu = 'FF  B0  {:02X}   {:02X}   10'.format(blockMSB, blockLSB)
						response, sw1, sw2 = cardservice.connection.transmit(toBytes(apdu))
						if (sw1,sw2) != (0x90,0x00):
							auth_discarded = True
							if args.verbose: print('Failed to read block {:02X} in sector {:02X} with Key B, SW: {}'.format(blockLSB, sector, toHexString([sw1,sw2])))
						else:
							auth_discarded = False
							sector_data[sector_block] = response
					break


		found_keys.append((key_a, key_b))
		data.append(sector_data)

		if sector_data[3] != []:
			# try to decode AC bits again if sector trailer has been read using Key B
			if blocks_access_condition_bits == None:
				access_conditions_bytes = sector_data[3][6:9]
				blocks_access_condition_bits = unpack_access_conditions_bits(access_conditions_bytes)
			sector_acs = [' '.join([str(y) for y in x]) for x in blocks_access_condition_bits]
		else:
			sector_acs = ['? ? ?', '? ? ?', '? ? ?', '? ? ?']

		acs.append(sector_acs)

	# pretty print collected data
	for i,s in enumerate(data):
		print('Sector {:02X}'.format(i))
		for j,b in enumerate(s):
			key_a, key_b = found_keys[i]
			print('  block {:02X}: '.format(i * 4 + j), end='')
			if b == []:
				print('?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ??', end='')
			else:
				print(toHexString(b), end='')
			print(', Key A: {}'.format('?? ?? ?? ?? ?? ??' if key_a == None else key_a), end='')
			print(', Key B: {}'.format('?? ?? ?? ?? ?? ??' if key_b == None else key_b), end='')
			print(', AC: {}'.format(acs[i][j]))


def unpack_access_conditions_bits(ac_bytes):
	bit = lambda pos, b: ((b >> pos) & 1)
	#ac0 = ac_bytes[0]  # we don't need that byte
	ac1 = ac_bytes[1]
	ac2 = ac_bytes[2]
	return [
		[bit(4, ac1), bit(0, ac2), bit(4, ac2)],
		[bit(5, ac1), bit(1, ac2), bit(5, ac2)],
		[bit(6, ac1), bit(2, ac2), bit(6, ac2)],
		[bit(7, ac1), bit(3, ac2), bit(7, ac2)]
	]


def can_read_block_with_key_a(ac_bits):
	return ac_bits in [ [0,0,0], [0,1,0], [1,0,0], [1,1,0], [0,0,1] ]


def can_read_key_b_bytes(ac_bits):
	return ac_bits in [ [0,0,0], [0,0,1], [0,1,0] ]


if __name__ == '__main__':
	main()