#!/usr/bin/python


#  mrpkey.py - calculate 3DES key for Machine Readable Passport
# 
#  Adam Laurie <adam@algroup.co.uk>
#  http://rfidiot.org/
# 
#  This code is copyright (c) Adam Laurie, 2006, 2007, All rights reserved.
#  For non-commercial use only, the following terms apply - for all other
#  uses, please contact the author:
#
#    This code is free software; you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation; either version 2 of the License, or
#    (at your option) any later version.
#
#    This code is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#

DEBUG= False
Filetype= ''
DocumentType= '?'

import RFIDIOtconfig
import sys
import os
import commands
from Crypto.Hash import SHA
from Crypto.Cipher import DES3
from Crypto.Cipher import DES
import random
import string
from operator import *
import StringIO
import Image
import ImageTk
from Tkinter import *

# Machine Readable Document types
DOC_UNDEF= '?'
DOC_ID= 'I<'
DOC_PASS= 'P<'

DOCUMENT_TYPE= {DOC_UNDEF:'Undefined',\
		DOC_ID:'ID Card',\
		DOC_PASS:'Passport'}

# TEST data
TEST_MRZ= 'L898902C<3UTO6908061F9406236ZE184226B<<<<<14'
TEST_rnd_ifd= '781723860C06C226'
TEST_rnd_icc= '4608F91988702212'
TEST_Kifd= '0B795240CB7049B01C19B33E32804F0B'
TEST_respdata= '46B9342A41396CD7386BF5803104D7CEDC122B9132139BAF2EEDC94EE178534F2F2D235D074D7449'
MRZ_WEIGHT= [7,3,1]
APDU_OK= '9000'
APDU_BAC= '6982'

# Data Groups and Elements
EF_COM= '60'
EF_DG1= '61'
EF_DG2= '75'
EF_DG3= '63'
EF_DG4= '76'
EF_DG5= '65'
EF_DG6= '66'
EF_DG7= '67'
EF_DG8= '68'
EF_DG9= '69'
EF_DG10= '6a'
EF_DG11= '6b'
EF_DG12= '6c'
EF_DG13= '6d'
EF_DG14= '6e'
EF_DG15= '6f'
EF_DG16= '70'
EF_SOD= '77'
EF_TAGS= '5c'

# Data Group Names
TAG_NAME= {EF_COM:'EF.COM Data Group Presence Map',\
	   EF_DG1:'EF.DG1 Data Recorded in MRZ',\
	   EF_DG2:'EF.DG2 Encoded Identification Features - FACE',\
	   EF_DG3:'EF.DG3 Encoded Identification Features - FINGER(s)',\
	   EF_DG4:'EF.DG4 Encoded Identification Features - IRIS(s)',\
	   EF_DG5:'EF.DG5 Displayed Identification Feature(s) - PORTRAIT',\
	   EF_DG6:'EF.DG6 Reserved for future use',\
	   EF_DG7:'EF.DG7 Displayed Identification Features - SIGNATURE or USUAL MARK',\
	   EF_DG8:'EF.DG8 Encoded Security Features - DATA FEATURE(s)',\
	   EF_DG9:'EF.DG9 Encoded Security Features - STRUCTURE FEATURE(s)',\
	   EF_DG10:'EF.DG10 Encoded Security Features - SUBSTANCE FEATURE(s)',\
	   EF_DG11:'EF.DG11 Additional Personal Detail(s)',\
	   EF_DG12:'EF.DG12 Additional Document Detail(s)',\
	   EF_DG13:'EF.DG13 Optional Detail(s)',\
	   EF_DG14:'EF.DG14 Reserved for Future Use',\
	   EF_DG15:'EF.DG15 Active Authentication Public Key Info',\
	   EF_DG16:'EF.DG16 Person(s) to Notify',\
	   EF_SOD:'EF.SOD Document Security Object',\
	   EF_TAGS:'Tag List'}

# Data Group Passport Application Long FID
TAG_FID=  {EF_COM:'011E',\
	   EF_DG1:'0101',\
	   EF_DG2:'0102',\
	   EF_DG3:'0103',\
	   EF_DG4:'0104',\
	   EF_DG5:'0105',\
	   EF_DG6:'0106',\
	   EF_DG7:'0107',\
	   EF_DG8:'0108',\
	   EF_DG9:'0109',\
	   EF_DG10:'010A',\
	   EF_DG11:'010B',\
	   EF_DG12:'010C',\
	   EF_DG13:'010D',\
	   EF_DG14:'010E',\
	   EF_DG15:'010F',\
	   EF_DG16:'0110',\
	   EF_SOD:'011D'}

# Filesystem paths
tempfiles= '/tmp/'
filespath= ''

# Data Group filenames for local storage
TAG_FILE= {EF_COM:'EF_COM.BIN',\
	   EF_DG1:'EF_DG1.BIN',\
	   EF_DG2:'EF_DG2.BIN',\
	   EF_DG3:'EF_DG3.BIN',\
	   EF_DG4:'EF_DG4.BIN',\
	   EF_DG5:'EF_DG5.BIN',\
	   EF_DG6:'EF_DG6.BIN',\
	   EF_DG7:'EF_DG7.BIN',\
	   EF_DG8:'EF_DG8.BIN',\
	   EF_DG9:'EF_DG9.BIN',\
	   EF_DG10:'EF_DG10.BIN',\
	   EF_DG11:'EF_DG11.BIN',\
	   EF_DG12:'EF_DG12.BIN',\
	   EF_DG13:'EF_DG13.BIN',\
	   EF_DG14:'EF_DG14.BIN',\
	   EF_DG15:'EF_DG15.BIN',\
	   EF_DG16:'EF_DG16.BIN',\
	   EF_SOD:'EF_SOD.BIN'}


# Data Group 1 Elements
DG1_ELEMENTS= {EF_DG1:'EF.DG1',\
	       '5f01':'LDS Version number with format aabb, where aa defines the version of the LDS and bb defines the update level',\
	       '5f36':'Unicode Version number with format aabbcc, where aa defines the Major version, bb defines the Minor version and cc defines the release level',\
	       '5c':'Tag list. List of all Data Groups present.'}
# Data Group 2 Elements
BDB= '5f2e'
BDB1= '7f2e'
FAC= '46414300'
DG2_ELEMENTS= {EF_DG2:'EF.DG2',\
	       '7f61':'Biometric Information Group Template',\
	       '02':'Integer - Number of instances of this type of biometric',\
	       '7f60':'1st Biometric Information Template',\
	       'a1':'Biometric Header Template (BHT)',\
	       '80':'ICAO header version [01 00] (Optional) - Version of the CBEFF patron header format',\
	       '81':'Biometric type (Optional)',\
	       '82':'Biometric feature (Optional for DG2, mandatory for DG3, DG4.)',\
	       '83':'Creation date and time (Optional)',\
	       '84':'Validity period (from through) (Optional)',\
	       '86':'Creator of the biometric reference data (PID) (Optional)',\
	       '87':'Format owner (Mandatory)',\
	       '88':'Format type (Mandatory)',\
	       BDB:'Biometric data (encoded according to Format Owner) also called the biometric data block (BDB).',\
	       BDB1:'Biometric data (encoded according to Format Owner) also called the biometric data block (BDB).',\
	       '7f60':'2nd Biometric Information Template',\
	       FAC:'Format Identifier ASCII FAC\0'}
# Data Group 2 field types
TEMPLATE= 0
SUB= 1
DG2_TYPE= {EF_DG2:TEMPLATE,\
	     '7f61':TEMPLATE,\
	     '02':SUB,\
	     '7f60':TEMPLATE,\
	     'a1':TEMPLATE,\
	     '80':SUB,\
	     '81':SUB,\
	     '82':SUB,\
	     '83':SUB,\
	     '84':SUB,\
	     '86':SUB,\
	     '87':SUB,\
	     '88':SUB,\
	     '5f2e':TEMPLATE,\
	     '7f2e':TEMPLATE,\
	     '7f60':TEMPLATE}

# ISO 19794_5 (Biometric identifiers)
ISO19794_5_GENDER= { '00':'Unpecified',\
		     '01':'Male',\
		     '02':'Female',\
		     '03':'Unknown'}

ISO19794_5_EYECOLOUR= { '00':'Unspecified',\
			'01':'Black',\
			'02':'Blue',\
			'03':'Brown',\
			'04':'Grey',\
			'05':'Green',\
			'06':'Multi',\
			'07':'Pink',\
			'08':'Other'}

ISO19794_5_HAIRCOLOUR= { '00':'Unspecified',\
			 '01':'Bald',\
			 '02':'Black',\
			 '03':'Blonde',\
			 '04':'Brown',\
			 '05':'Grey',\
			 '06':'White',\
			 '07':'Red',\
			 '08':'Green',\
			 '09':'Blue',\
			 'ff':'Other'}	

ISO19794_5_FEATURE= {0x01:'Specified',\
		     0x02:'Glasses',\
		     0x04:'Moustache',\
		     0x08:'Beard',\
		     0x10:'Teeth Visible',\
		     0x20:'Blink',\
		     0x40:'Mouth Open',\
		     0x80:'Left Eyepatch',\
		     0x100:'Right Eyepatch',\
		     0x200:'Dark Glasses',\
		     0x400:'Distorted'}

ISO19794_5_EXPRESSION= {'0000':'Unspecified',\
			'0001':'Neutral',\
		  	'0002':'Smile Closed',\
		  	'0003':'Smile Open',\
		  	'0004':'Raised Eyebrow',\
		  	'0005':'Looking Away',\
		  	'0006':'Squinting',\
		  	'0007':'Frowning'}

ISO19794_5_IMG_TYPE= {'00':'Unspecified (Front)',\
		      '01':'Basic',\
		      '02':'Full Front',\
		      '03':'Token Front',\
		      '04':'Other'}

ISO19794_5_IMG_DTYPE= {'00':'JPEG',\
		       '01':'JPEG 2000'}

ISO19794_5_IMG_FTYPE= {'00':'JPG',\
		       '01':'JP2'}

ISO19794_5_IMG_CSPACE= {'00':'Unspecified',\
			'01':'RGB24',\
			'02':'YUV422',\
			'03':'GREY8BIT',\
			'04':'Other'}

ISO19794_5_IMG_SOURCE= {'00':'Unspecified',\
			'01':'Static Unspecified',\
			'02':'Static Digital',\
			'03':'Static Scan',\
			'04':'Video Unknown',\
			'05':'Video Analogue',\
			'06':'Video Digital',\
			'07':'Unknown'}

ISO19794_5_IMG_QUALITY= {'00':'Unspecified'}


# display options
MRZ_FIELD_NAMES= ('Document code','Issuing State or Organisation','Name','Passport Number','Check Digit','Nationality','Date of Birth','Check Digit','Sex','Date of Expiry','Check Digit','Personal Number or other optional elements','Check Digit','Composite Check Digit')
MRZ_FIELD_LENGTHS= (2,3,39,9,1,3,6,1,1,6,1,14,1,1)
MRZ_FIELD_DISPLAY= (0,3,1,2,5,6,8,9,11)
MRZ_FIELD_NAMES_ID= ('Document code','Issuing State or Organisation','Document Number','Check Digit','Personal Number or other optional elements','Check Digit','Date of Birth','Check Digit','Sex','Date of Expiry','Check Digit','Nationality','Check Digit','Name')
MRZ_FIELD_LENGTHS_ID= (2,3,9,1,14,1,6,1,1,6,1,14,1,30)
MRZ_FIELD_DISPLAY_ID= (0,2,1,13,11,6,8,9,4)

# Global Send Sequence Counter
SSC= ''

# Global bruteforce vars
num= []
map= []
brnum= 0

def mrzspaces(data, fill):
	out= ''
	for x in range(len(data)):
		if data[x] == '<':
			out += fill
		else:
			out += data[x]
	return out

class guiviewpass:
	def __init__(self,root,filename,mrz):
		global Filetype
		global DocumentType

		font= 'OCRB 22'
		fonta= 'OCRA 22'
		frame = Frame(root, colormap="new", visual='truecolor').grid()
		root.title('%s (RFIDIOt v%s)' % (myver,passport.VERSION))
		if Filetype == "JP2":
			# nasty hack to deal with JPEG 2000 until PIL support comes along
			(exitstatus, output) = commands.getstatusoutput("convert %sJP2 %sJPG" % (tempfiles+filename,tempfiles+filename))
			print "      (converted %sJP2 to %sJPG for display)" % (tempfiles+filename,tempfiles+filename)
			if exitstatus:
				print 'Could not convert JPEG 2000 image (%d) - please install ImageMagick' % exitstatus
				os._exit(False)
			Filetype= 'JPG'
		# display data according to document type
		if DocumentType == DOC_ID:
			fields= MRZ_FIELD_DISPLAY_ID
			fieldnames= MRZ_FIELD_NAMES_ID
			fieldlengths= MRZ_FIELD_LENGTHS_ID
		else:
			fields= MRZ_FIELD_DISPLAY
			fieldnames= MRZ_FIELD_NAMES
			fieldlengths= MRZ_FIELD_LENGTHS
		self.imagedata = ImageTk.PhotoImage(file=tempfiles + filename + Filetype)
		self.image = Label(frame, image=self.imagedata, borderwidth= 15).grid(row= 0, sticky= W, rowspan= 20)
		self.quitbutton = Button(frame, text="Quit", command=root.quit).grid(row= 0, column=3, sticky= NE, rowspan= 2)
		self.label = Label(frame, text='Type').grid(row= 1, sticky= W, column= 1)
		self.label = Label(frame, text=DOCUMENT_TYPE[DocumentType], font= font).grid(row= 2, sticky= W, column= 1)
		row= 3
		for item in fields:
			self.label = Label(frame, text=fieldnames[item]).grid(row= row, sticky= W, column= 1)
			row += 1
			mrzoffset= 0
			for x in range(item):
				mrzoffset += fieldlengths[x]
			if fieldnames[item] == "Issuing State or Organisation" or fieldnames[item] == "Nationality":
				self.label = Label(frame, text= mrzspaces(mrz[mrzoffset:mrzoffset + fieldlengths[item]],' ') + '  ' + passport.ISO3166CountryCodesAlpha[mrzspaces(mrz[mrzoffset:mrzoffset + fieldlengths[item]],'')], font= font).grid(row= row, sticky= W, column= 1)
			else:
				self.label = Label(frame, text=mrzspaces(mrz[mrzoffset:mrzoffset + fieldlengths[item]],' '), font= font).grid(row= row, sticky= W, column= 1)
			row += 1
#				row += 1
		self.label = Label(frame, text='  ' + mrz[:len(mrz) / 2], font= fonta, justify= 'left').grid(row= row, sticky= W, columnspan= 4)
		row += 1
		self.label = Label(frame, text='  ' + mrz[len(mrz) / 2:], font= fonta, justify= 'left').grid(row= row, sticky= W, columnspan= 4)
		row += 1
		self.label = Label(frame, text='http://rfidiot.org').grid(row= row, sticky= W, column= 1)

def getrandom(size):
	data= ''
	for x in range(size):
		data += '%02x' % int(random.uniform(0,0xff))
	return data

def get_challenge(length):
	"get random challenge"
	ins= 'GET_CHALLENGE'
	le= '%02x' % length
	if DEBUG:
		print "DEBUG: requesting %d byte challenge" % length
	if passport.send_apdu('','','','','',ins,'','','','',le):
		return passport.data
	passport.iso_7816_fail(passport.errorcode)

def mutual_authenticate(data,key):
	"mutual authenticate"
	global brnum
	ins= 'EXTERNAL_AUTHENTICATE'
	lc= '28'
	hexdata= ''
	le= '28'
	for x in range(len(data)):
		hexdata += '%02x' % ord(data[x])
	if passport.send_apdu('','','','','',ins,'','',lc,hexdata,le):
		out= passport.data
	if passport.errorcode == APDU_OK:
		if passport.MACVerify(out,key):
			print 'OK'
			return out
		else:
			os._exit(False)
	else:
		if brnum == 0:
			passport.iso_7816_fail(passport.errorcode)
		else:
			return ''

def select_file(file):
	"select file"
	ins= 'SELECT_FILE'
	p1= '02'
	p2= '0C'
	lc= '02'
	data= file
	passport.send_apdu('','','','','',ins,p1,p2,lc,data,'')
	if passport.errorcode == APDU_BAC:
		return False
	else:
		return True

def select_passport_app():
	"select passport specific application (AID: A0000002471001)"
	ins= 'SELECT_FILE'
	p1= '04'
	p2= '0C'
	lc= '07'
	data= 'A0000002471001'
	passport.send_apdu('','','','','',ins,p1,p2,lc,data,'')
	if passport.errorcode == APDU_OK:
		return True
	else:
		return False

def secure_select_file(keyenc, keymac,file):
	"secure select file"
	global SSC

	cla= '0c'
	ins= passport.ISOAPDU['SELECT_FILE']
	p1= '02'
	p2= '0c'
	command= passport.PADBlock(passport.ToBinary(cla + ins + p1 + p2))
	data= passport.PADBlock(passport.ToBinary(file))
	tdes= DES3.new(keyenc,DES.MODE_CBC,passport.DES_IV)
	encdata= tdes.encrypt(data)
	if DEBUG:
		print 'Encrypted data: ',
		passport.HexPrint(encdata)
	do87= passport.ToBinary(passport.DO87) + encdata
	m= command + do87
	if DEBUG:
		print 'DO87: ',
		passport.HexPrint(m)
	SSC= passport.SSCIncrement(SSC)
	n= SSC + m
	cc= passport.DESMAC(n,keymac,'')
	if DEBUG:
		print 'CC: ',
		passport.HexPrint(cc)
	do8e= passport.ToBinary(passport.DO8E) + cc
	if DEBUG:
		print 'DO8E: ',
		passport.HexPrint(do8e)
	lc= "%02x" % (len(do87) + len(do8e))
	le= '00'
	data= passport.ToHex(do87 + do8e)
	if DEBUG:
		print
		print 'Protected APDU: ',
		print cla+ins+p1+p2+lc+data+le
	ins= 'SELECT_FILE'
	if passport.send_apdu('','','','',cla,ins,p1,p2,lc,data,le):
		out= passport.data
	if DEBUG:
		print 'Secure Select:',
	if passport.errorcode == APDU_OK:
		if DEBUG:
			print 'OK'
		check_cc(keymac,out)
		return out
	else:
		passport.iso_7816_fail(passport.errorcode)

def secure_read_binary(keymac,bytes,offset):
	"secure read binary data"
	global SSC


	cla= '0c'
	ins= passport.ISOAPDU['READ_BINARY']
	hexoffset= '%04x' % offset
	p1= hexoffset[0:2]
	p2= hexoffset[2:4]
	le= '%02x' % bytes
	command= passport.PADBlock(passport.ToBinary(cla + ins + p1 + p2))
	do97= passport.ToBinary(passport.DO97 + le)
	m= command + do97 
	SSC= passport.SSCIncrement(SSC)
	n= SSC + m
	cc= passport.DESMAC(n,keymac,'')
	do8e= passport.ToBinary(passport.DO8E) + cc
	lc= "%02x" % (len(do97) + len(do8e))
	le= '00'
	data= passport.ToHex(do97 + do8e)
	if DEBUG:
		print
		print 'Protected APDU: ',
		print cla+ins+p1+p2+lc+data+le
	ins= 'READ_BINARY'
	if passport.send_apdu('','','','',cla,ins,p1,p2,lc,data,le):
		out= passport.data
	if DEBUG:
		print 'Secure Read Binary (%02d bytes): ' % bytes,
	if passport.errorcode == APDU_OK:
		if DEBUG:
			print 'OK:', out
		check_cc(keymac,out)
		return out
	else:
		passport.iso_7816_fail(passport.errorcode)

def read_binary(bytes,offset):
	ins= 'READ_BINARY'
	hexoffset= '%04x' % offset
	p1= hexoffset[0:2]
	p2= hexoffset[2:4]
	le= '%02x' % bytes
	if passport.send_apdu('','','','','',ins,p1,p2,'','',le):
		out= passport.data
	return out
	
def calculate_check_digit(data):
	"calculate ICAO 9303 check digit"
	cd= n= 0
	for d in data:
		if 'A' <= d <= 'Z':
			value = ord(d)-55
		elif d == '<':
			value = 0
		else:
			value = int(d)
		cd += value * MRZ_WEIGHT[n % 3]
		n += 1
	return '%s' % (cd % 10)

def check_cc(key,rapdu):
	"Check Cryptographic Checksum"
	global SSC

	SSC= passport.SSCIncrement(SSC)
	k= SSC
	length= 0
	# check if DO87 present
	if rapdu[0:2] == "87":
		length= 4 + int(rapdu[2:4],16) * 2
		k += passport.ToBinary(rapdu[:length])
	# check if DO99 present
	if rapdu[length:length + 2] == "99":
		length2= 4 + int(rapdu[length + 2:length + 4],16) * 2
		k += passport.ToBinary(rapdu[length:length + length2])
	
	if DEBUG:
		print 'K: ',
		passport.HexPrint(k)
	cc= passport.DESMAC(k,key,'')
	if DEBUG:
		print 'CC: ',
		print passport.ToHex(cc),
	if cc ==  passport.ToBinary(rapdu[len(rapdu) - len(cc) *2:]):
		if DEBUG:
        		print '(verified)'
		return True
	else:
        	print 'Cryptographic Checksum failed!'
        	print 'Expected CC: ',
        	passport.HexPrint(cc)
        	print 'Received CC: ',
        	print rapdu[len(rapdu) - len(cc) * 2:]
		os._exit(False)

def decode_ef_com(data):
	TAG_PAD= '80'

	# set up array for Data Groups to be read
	ef_groups= []

	"display contents of EF.COM"
	hexdata= passport.ToHex(data)
	# skip header
	pos= 2
	# EF.COM length
	print 'Length: ', asn1datalength(hexdata[pos:])
	pos += asn1fieldlength(hexdata[pos:])
	while pos < len(hexdata):
		# end of data
		if hexdata[pos:pos+2] == TAG_PAD:
			return
		# LDS & Unicode Versions
		decoded= False
		for length in 2,4:
                       	if DG1_ELEMENTS.has_key(hexdata[pos:pos + length]):
				decoded= True
				print '  tag:',hexdata[pos:pos + length],'('+DG1_ELEMENTS[hexdata[pos:pos+length]]+')'
				# decode tag list (stored objects)
				if hexdata[pos:pos+length] == EF_TAGS:
					pos += 2
					print '    length: ',
					length= asn1datalength(hexdata[pos:])
					print length
					pos += asn1fieldlength(hexdata[pos:])
					for n in range(length):
						print '      Data Group: ',
						print hexdata[pos:pos+2] + ' (' + TAG_NAME[hexdata[pos:pos+2]] + ')'
						ef_groups.append(hexdata[pos:pos+2])
						pos += 2
				else:
					pos += length
					fieldlength= asn1datalength(hexdata[pos:])
					print '    length:',fieldlength
					pos += asn1fieldlength(hexdata[pos:])
					print '    data:',hexdata[pos:pos+fieldlength*2]
					pos += fieldlength*2
		if not decoded:
			print 'Unrecognised element:', hexdata[pos:pos+4]
			os._exit(False)
	return ef_groups

def read_file(file):
	select_file(file)
	readlen= 4
	out= read_binary(readlen,0)
	print passport.errorcode
	passport.HexPrint(out)
	
def asn1fieldlength(data):
	#return length of number field according to asn.1 rules (doubled as we normally care about the hex version)
	if int(data[:2],16) <= 0x7f:
		return 2
	if int(data[:2],16) == 0x81:
		return 4
	if int(data[:2],16) == 0x82:
		return 6

def asn1datalength(data):
	#return actual length represented by asn.1 field
	if int(data[:2],16) <= 0x7f:
		return int(data[:2],16)
	if int(data[:2],16) == 0x81:
		return  int(data[2:4],16)
	if int(data[:2],16) == 0x82:
		return int(data[2:6],16)

def secure_read_file(keyenc,keymac,file):
	# this needs fixing - MAX should be able to go up to size supported by device
	MAXCHUNK= 118
#	MAXCHUNK= int(passport.ISO_FRAMESIZE[passport.framesize])

	rapdu= secure_select_file(keyenc,keymac,file)
	# secure read file header (header byte plus up to 3 bytes of field length)
	readlen= 4
	offset= 4
	rapdu= secure_read_binary(keymac,readlen,0)
	do87= rapdu[6:22]
	if DEBUG:
		print 'DO87: ' + do87
		print 'Decrypted DO87: ',
	tdes=  DES3.new(keyenc,DES.MODE_CBC,passport.DES_IV)
	decdo87= tdes.decrypt(passport.ToBinary(do87))[:readlen]
	if DEBUG:
		passport.HexPrint(decdo87)

	# get file length
	do87hex= passport.ToHex(decdo87)
	tag= do87hex[:2]
	datalen= asn1datalength(do87hex[2:])
	print 'File Length:', datalen
	# deduct length field and header from what we've already read
	readlen= datalen - (3 - asn1fieldlength(do87hex[2:]) / 2)
	print 'Remaining data length:', readlen
	# secure read remaining bytes
	while readlen > 0:
		if readlen > MAXCHUNK:
			toread= MAXCHUNK
		else:
			toread= readlen
		rapdu= secure_read_binary(keymac,toread,offset)
		do87= rapdu[6:(toread + (8 - toread % 8)) * 2 + 6]
		tdes=  DES3.new(keyenc,DES.MODE_CBC,passport.DES_IV)
		decdo87 += tdes.decrypt(passport.ToBinary(do87))[:toread]
		offset += toread
		readlen -= toread
		print 'Reading: %05d\r' % readlen,
		sys.stdout.flush()
	print
	return decdo87

def decode_ef_dg1(data):
	global DocumentType

	length= int(passport.ToHex(data[4]),16)
	print 'Data Length: ',
	print length
	pointer= 5
	out= ''
	while pointer < len(data):
		if data[pointer] == chr(0x80):
			break
		out += '%s' % chr(int(passport.ToHex(data[pointer]),16))
		pointer += 1
	print '  Decoded Data: ' + out
	DocumentType= out[0:2]
	print '    Document type: %s' % DOCUMENT_TYPE[DocumentType]
	if DocumentType == DOC_ID:
		fieldnames= MRZ_FIELD_NAMES_ID
		fieldlengths= MRZ_FIELD_LENGTHS_ID
	else:
		fieldnames= MRZ_FIELD_NAMES
		fieldlengths= MRZ_FIELD_LENGTHS
	pointer= 0
	for n in range(len(fieldnames)):
		print '    ' + fieldnames[n] + ': ',
		print out[pointer:pointer + fieldlengths[n]]
		pointer += fieldlengths[n]
	return(out)

def decode_ef_dg2(data):
	global Filetype

	datahex= passport.ToHex(data)
	position= 0
	end= len(datahex)
	while position < end:
		decoded= False
		# check for presence of tags
		for length in 4,2:
			if DG2_ELEMENTS.has_key(datahex[position:position + length]):
				decoded= True
				tag= datahex[position:position + length]
				print '  Tag:', tag, '('+DG2_ELEMENTS[tag]+')'
				# don't skip TEMPLATE fields as they contain sub-fields
				# except BDB which is a special case (CBEFF formatted) so for now
				# just try and extract the image which is 65 bytes in
				if DG2_TYPE[tag] == TEMPLATE:
					position += length
					fieldlength= asn1datalength(datahex[position:])
					print '     length:', fieldlength
					if tag == BDB or tag == BDB1:
						# process CBEFF block
						position += asn1fieldlength(datahex[position:])
						# FACE header
						length= len(FAC)
						tag= datahex[position:position + length]
						if not tag == FAC:
							print 'Missing FAC in CBEFF block: %s' % tag
							os._exit(False)
						position += length
						# FACE version
						print '    FACE version: %s' % passport.ToBinary(datahex[position:position + 6])
						position += 8
						# Image length
						print '      Record Length: %d' % int(datahex[position:position + 8],16)
						imagelength= int(datahex[position:position + 8],16)
						position += 8
						# Number of Images
						images= int(datahex[position:position + 4],16)
						print '      Number of Images: %d' % images
						position += 4
						# Facial Image block
						print '      Block Length: %d' % int(datahex[position:position + 8],16)
						position += 8
						features= int(datahex[position:position + 4],16)
						print '      Number of Features: %d' % features
						position += 4
						print '      Gender: %s' % ISO19794_5_GENDER[datahex[position:position + 2]]
						position += 2
						print '      Eye Colour: %s' % ISO19794_5_EYECOLOUR[datahex[position:position + 2]]
						position += 2
						print '      Hair Colour: %s' % ISO19794_5_HAIRCOLOUR[datahex[position:position + 2]]
						position += 2
						mask= int(datahex[position:position + 6],16)
						print '      Feature Mask: %s' % datahex[position:position + 6]
						position += 6
						if features:
							print '      Features:'
							for m, d in ISO19794_5_FEATURE.items():
								if and_(mask,m):
									print '        : %s' % d
						print '      Expression: %s' % ISO19794_5_EXPRESSION[datahex[position:position + 4]]
						position += 4
						print '      Pose Angle: %s' % datahex[position:position + 6]
						position += 6
						print '      Pose Angle Uncertainty: %s' % datahex[position:position + 6]
						position += 6
						while features > 0:
							print '      Feature block: %s' % datahex[position:position + 16]
							features -= 1
							position += 16
						print '      Image Type: %s' % ISO19794_5_IMG_TYPE[datahex[position:position + 2]]
						position += 2
						print '      Image Data Type: %s' % ISO19794_5_IMG_DTYPE[datahex[position:position + 2]]
						Filetype= ISO19794_5_IMG_FTYPE[datahex[position:position + 2]]
						position += 2
						print '      Image Width: %d' % int(datahex[position:position + 4],16)
						position += 4
						print '      Image Height: %d' % int(datahex[position:position + 4],16)
						position += 4
						print '      Image Colour Space: %s' % ISO19794_5_IMG_CSPACE[datahex[position:position + 2]]
						position += 2
						print '      Image Source Type: %s' % ISO19794_5_IMG_SOURCE[datahex[position:position + 2]]
                                                position += 2
						print '      Image Device Type: %s' % datahex[position:position + 6]
                                                position += 6
						print '      Image Quality: %s' % ISO19794_5_IMG_QUALITY[datahex[position:position + 2]]
						position += 2
						img= open(tempfiles+'EF_DG2.' + Filetype,'w')
						img.write(data[position / 2:position + imagelength])
						img.flush()
						img.close()
						print '     JPEG image stored in %sEF_DG2.%s' % (tempfiles,Filetype)
						position += imagelength * 2
					else:
						position += asn1fieldlength(datahex[position:])
				else:
					position += length
					fieldlength= asn1datalength(datahex[position:])
					print '     length:', fieldlength
					position += asn1fieldlength(datahex[position:])
					print '     data:', datahex[position:position + fieldlength * 2]
					position += fieldlength * 2
		if not decoded:
			print 'Unrecognised element:', datahex[position:position + 4]
			os._exit(False)
	
def bruteno(init):
	global num
	global map
	global brnum
	global width

	if init:
		# set up brute force and return number of iterations required
		width= 0
		for x in range(len(init)):
			if init[x] == '?':
				width += 1
				num.append(0)
				map.append(True)
			else:
				num.append(init[x])
				map.append(False)
		return pow(10, width)
	else:
		out= ''
		bruted= False
		for x in range(len(num)):
			if map[x]:
				if bruted:
					continue
				else:
					bruted= True
					out += '%0*d' % (width, brnum)
					brnum += 1
			else:
				out += num[x]
		return out

try:
        passport= RFIDIOtconfig.card
except:
        os._exit(False)

args= RFIDIOtconfig.args
help= RFIDIOtconfig.help
DEBUG= RFIDIOtconfig.debug

myver= 'mrpkey v0.1n'
passport.info(myver)

TEST= False
FILES= False
bruteforce= False
bruteforceno= False

if help or not len(args) == 1 or (not len(args[0]) == 44 and not args[0] == 'TEST' and not args[0] == 'CHECK' and not os.access(args[0],os.F_OK)):
	print
	print 'Usage:'
	print '\t' + sys.argv[0] + ' [OPTIONS] <MRZ (Lower)|CHECK|FILES|[PATH]>'
	print
	print '\tSpecify the Lower MRZ as a quoted string or the word TEST to use sample data.'
	print '\tSpecify the word CHECK to check if the device is a passport.'
	print '\tSpecify a PATH to use files that were previously read from a passport.'
	print '\tSpecify \'?\' for check digits if not known and they will be calculated.'
	print '\tSpecify \'?\' in the passport number field for bruteforce of that portion.'
	print '\tNote: only one contiguous portion of the field may be bruteforced.'
	print '\tPadding character \'<\' should be used for unknown fields.'
	print
	RFIDIOtconfig.printoptions()
        os._exit(True)

print

# check if we are reading from files
if os.access(args[0],os.F_OK):
	FILES= True
	filespath= args[0]
	if not filespath[len(filespath) - 1] == '/':
		filespath += '/'
	try:
		passfile= open(filespath + 'EF_COM.BIN')
	except:
		print "Can't open %s" % (filespath + 'EF_COM.BIN')
		os._exit(False)
	data= passfile.read()
	eflist= decode_ef_com(data)
	passfile.close()

if args[0] == 'TEST':
	TEST= True

if args[0] == 'CHECK':
	if not passport.hsselect('08'):
		print 'No selectable device found!'
		os._exit(False)
	if select_passport_app():
		print 'Device is a Machine Readable Document'
		os._exit(True)
	else:
		print 'Device is NOT a Machine Readable Document'
		os._exit(False)

if TEST:
	passport.MRPmrzl(TEST_MRZ)
	print 'Test MRZ: ' + TEST_MRZ
if not TEST and not FILES:
	passport.MRPmrzl(string.upper(args[0]))

if not FILES:
	print 'Passport number: ' + passport.MRPnumber
	if passport.MRPnumber.find('?') >= 0:
		bruteforce= True
		bruteforceno= True
		# initialise bruteforce for number
		iterations= bruteno(passport.MRPnumber)
		print 'Bruteforcing Passport Number (%d iterations)' % iterations
	else:
		iterations= 1
	print 'Nationality: ' + passport.MRPnationality
	print 'Date Of Birth: ' + passport.MRPdob
	print 'Sex: ' + passport.MRPsex
	print 'Expiry: ' + passport.MRPexpiry
	print 'Optional: ' + passport.MRPoptional

	# loop until successful login breaks us out or we've tried all possibilities
	while iterations:
		iterations -= 1
		if bruteforceno:
			passport.MRPnumber= bruteno('')
		# always calculate check digits (makes bruteforcing easier)
		passport.MRPnumbercd= calculate_check_digit(passport.MRPnumber)
		passport.MRPdobcd= calculate_check_digit(passport.MRPdob)
		passport.MRPexpirycd= calculate_check_digit(passport.MRPexpiry)
		passport.MRPoptionalcd= calculate_check_digit(passport.MRPoptional)
		passport.MRPcompsoitecd= calculate_check_digit(passport.MRPnumber + passport.MRPnumbercd + passport.MRPnationality + passport.MRPdob + passport.MRPdobcd + passport.MRPsex + passport.MRPexpiry + passport.MRPexpirycd + passport.MRPoptional + passport.MRPoptionalcd)

		kmrz= passport.MRPnumber + passport.MRPnumbercd + passport.MRPdob + passport.MRPdobcd + passport.MRPexpiry + passport.MRPexpirycd

		print
		print 'Generate local keys:'
		print
		print 'Key MRZ Info (kmrz): ' + kmrz
		print
		kseedhash= SHA.new(kmrz)
		kseed= kseedhash.digest()[:16]
		if DEBUG:
			print 'Kseed (SHA1 hash digest of kmrz): ' + kseedhash.hexdigest()[:32]

		# calculate Kenc & Kmac
		Kenc= passport.DESKey(kseed,passport.KENC,16)
		if DEBUG:
			print 'Kenc: ',
			passport.HexPrint(Kenc)
		Kmac= passport.DESKey(kseed,passport.KMAC,16)
		if DEBUG:
			print 'Kmac: ',
			passport.HexPrint(Kmac)
			print

		if TEST:
			rnd_ifd= TEST_rnd_ifd
			rnd_icc= TEST_rnd_icc
			Kifd= TEST_Kifd
		else:
			# set communication speed
			# 01 = 106 kBaud
			# 02 = 212 kBaud
			# 04 = 414 kBaud
			# 08 = 818 kBaud
			if not passport.hsselect('08'):
				print 'Could not select passport! (%s)' % passport.errorcode
				os._exit(False)
			print 'Device set to %s transfers' % passport.ISO_SPEED[passport.speed]
			print 'Device supports %s Byte transfers' % passport.ISO_FRAMESIZE[passport.framesize]
			print
			print 'Select Passport Application (AID): ',
			if select_passport_app():
				print 'OK'
			else:
				passport.iso_7816_fail(passport.errorcode)
			print 'Select Master File: ',
			if select_file(TAG_FID[EF_COM]):
				print 'No Basic Access Control!'
				# try forcing BAC by reading a file
				print 'Trying read without BAC: ',
				read_file(TAG_FID[EF_DG1])
			else:
				print 'Basic Acces Control Enforced!'
			if DEBUG:
				print 'Get Challenge from Passport (rnd_icc): ',
			rnd_icc= get_challenge(8)
			if DEBUG:
				passport.HexPrint(rnd_icc)
			rnd_ifd= getrandom(8)
			Kifd= getrandom(16)

		if DEBUG or TEST:
			print 'Generate local random Challenge (rnd_ifd): ' + rnd_ifd
			print 'Generate local random Challenge (Kifd): ' + Kifd
			print

		S= passport.ToBinary(rnd_ifd + rnd_icc + Kifd)

		if DEBUG or TEST:
			print 'S: ',
			passport.HexPrint(S)

		if DEBUG or TEST:
			print 'Kenc: ',
			passport.HexPrint(Kenc)


		tdes= DES3.new(Kenc,DES.MODE_CBC,passport.DES_IV)
		Eifd= tdes.encrypt(S)
		if DEBUG or TEST:
			print 'Eifd: ',
			passport.HexPrint(Eifd)
			print 'Kmac: ',
			passport.HexPrint(Kmac)
		Mifd= passport.DESMAC(Eifd,Kmac,'')
		if DEBUG or TEST:
			print 'Mifd: ',
			passport.HexPrint(Mifd)

		cmd_data= Eifd + Mifd
		if DEBUG or TEST:
			print 'cmd_data: ',
			passport.HexPrint(cmd_data)
			print

		if TEST:
			respdata= TEST_respdata
		else:
			print 'Authenticating: ',
			respdata= mutual_authenticate(cmd_data,Kmac)

		if DEBUG or TEST:
			print 'Auth Response: ' + respdata
		resp= respdata[:64]
		respmac= respdata[64:80]
		if DEBUG or TEST:
			print 'Auth message: ' + resp
			print 'Auth MAC: ' + respmac + ' (verified)'
		decresp= passport.ToHex(tdes.decrypt(passport.ToBinary(resp)))
		if DEBUG or TEST:
			print 'Decrypted Auth Response: ' + decresp
			print 'Decrypted rnd_icc: ' + decresp[:16]
		recifd= decresp[16:32]
		if DEBUG or TEST:
			print 'Decrypted rnd_ifd: ' + recifd,
		# check returned rnd_ifd matches our challenge
		if not passport.ToBinary(recifd) == passport.ToBinary(rnd_ifd):
			print 'Challenge failed!'
			print 'Expected rnd_ifd: ', rnd_ifd
			print 'Received rnd_ifd: ', recifd
			if not bruteforce or iterations == 0:
				os._exit(False)
		else:
			if DEBUG or TEST:
				print '(verified)'
			# challenge succeeded, so break
			break

	kicc= decresp[32:64] 
	if DEBUG or TEST:
		print 'Decrypted Kicc: ' + kicc

	# generate session keys
	print
	print 'Generate session keys: '
	print
	kseedhex= "%032x" % xor(int(Kifd,16),int(kicc,16))
	kseed= passport.ToBinary(kseedhex)
	print 'Kifd XOR Kicc (kseed): ',
	passport.HexPrint(kseed)
	KSenc= passport.DESKey(kseed,passport.KENC,16)
	print 'Session Key ENC: ',
	passport.HexPrint(KSenc)
	KSmac= passport.DESKey(kseed,passport.KMAC,16)
	print 'Session Key MAC: ',
	passport.HexPrint(KSmac)

	print
	# calculate Send Sequence Counter
	print 'Calculate Send Sequence Counter: '
	print
	SSC= passport.ToBinary(rnd_icc[8:16] + rnd_ifd[8:16])
	print 'SSC: ',
	passport.HexPrint(SSC)

	# secure select master file
	if TEST:
		KSmac= passport.ToBinary('F1CB1F1FB5ADF208806B89DC579DC1F8')
		rapdu= '990290008E08FA855A5D4C50A8ED9000'
		# ran out of steam on testing here! 
		os._exit(True)
	else:
		data= secure_read_file(KSenc,KSmac,TAG_FID[EF_COM])

	# secure read file header
	#if TEST:
	#	KSmac= passport.ToBinary('F1CB1F1FB5ADF208806B89DC579DC1F8')
	#	rapdu= '8709019FF0EC34F9922651990290008E08AD55CC17140B2DED9000'
	#else:
	#	readlen= 4
	#	rapdu= secure_read_binary(KSmac,readlen,0)

	print 'EF.COM: ',
	if DEBUG:
		passport.HexPrint(data)
	eflist= decode_ef_com(data)
	efcom= open(tempfiles+TAG_FILE[EF_COM],'w+')
	efcom.write(data)
	efcom.flush()
	efcom.close()
	print 'EF.COM stored in', tempfiles+TAG_FILE[EF_COM]

# get SOD
#print
#print 'Select EF.SOD: ',
#data= secure_read_file(KSenc,KSmac,TAG_FID[EF_SOD])
#if DEBUG:
#	passport.HexPrint(data)
#sod= open(tempfiles+TAG_FILE[EF_SOD],'w+')
#sod.write(data)
#sod.flush()
#sod.close()
#print 'EF.SOD stored in', tempfiles+TAG_FILE[EF_SOD]

# Add Security Object to list for reading
eflist.insert(0,EF_SOD)
# now get everything else
for tag in eflist:
	print 'Reading:', TAG_NAME[tag]
	if not FILES:
		data= secure_read_file(KSenc,KSmac,TAG_FID[tag])
	else:
		try:
			passfile= open(filespath+TAG_FILE[tag])
		except:
			print "Can't open %s" % filespath+TAG_FILE[tag]
			os._exit(False)
		data= passfile.read()
	
	if DEBUG:
		passport.HexPrint(data)
	outfile= open(tempfiles+TAG_FILE[tag],'w+')
	outfile.write(data)
	outfile.flush()
	outfile.close()
	print '  Stored in', tempfiles+TAG_FILE[tag]
	# special cases
	if tag == EF_SOD:
		# extract DER file (should be at offset 4 - if not, use sod.py to find it in EF_SOD.BIN
		# temporary evil hack until I have time to decode EF.SOD properly
		outfile= open(tempfiles+"EF_SOD.TMP",'w')
		outfile.write(data[4:])
		outfile.flush()
		outfile.close()
		(exitstatus, outtext) = commands.getstatusoutput("openssl pkcs7 -text -print_certs -in %sEF_SOD.TMP -inform DER" % tempfiles) 
		if not exitstatus and len(outtext) > 0:
			commands.getoutput("openssl pkcs7 -in %sEF_SOD.TMP -out %sEF_SOD.PEM -inform DER" % (tempfiles,tempfiles))
			print commands.getoutput("openssl pkcs7 -text -print_certs -in %sEF_SOD.PEM" % tempfiles)
			print 
			print 'Certificate stored in %sEF_SOD.PEM' % tempfiles
	if tag == EF_DG1:
		mrz= decode_ef_dg1(data)
	if tag == EF_DG2:
		decode_ef_dg2(data)

# image read is nasty hacky bodge to see if image display without interpreting the headers
# start of image location may change - look for JPEG header bytes 'FF D8 FF E0'
# german is JP2 format, not JPG - look for '00 00 00 0C 6A 50'
# in /tmp/EF_DG2.BIN

# display data and image in gui window
root = Tk()
#root.withdraw()
view= guiviewpass(root,'EF_DG2.',mrz)
root.mainloop()
