aboutsummaryrefslogtreecommitdiffstats
path: root/fingerpass.py
blob: 1a2d22b0da4f501e7552f34c984d525a41c216a9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
#!/usr/bin/env python
# -*- coding: iso-8859-1 -*-

import utils, cards, TLV_utils, sys, binascii, time, traceback, re, readers

def fingerprint_rfid(card):
    # Need RFID
    if not isinstance(card, cards.rfid_card.RFID_Card):
        return []
    
    uid = card.get_uid()
    
    return ["%02X" % ord(uid[0])]
    # FIXME: Determine ISO type and then return a value depending on A-fixed UID vs. A-random UID vs. B

def fingerprint_7816(card):
    # Need ISO 7816-4
    if not isinstance(card, cards.iso_7816_4_card.ISO_7816_4_Card):
        return []
    
    # Try a select MF, just in case ...
    try:
        card.change_dir()
    except (SystemExit, KeyboardInterrupt):
        raise
    except:
        traceback.print_exc()
    
    SHORT_SW_MAP = {
        "\x90\x00": 0,
        "\x69\x82": 1, # Security status not satisfied
        "\x6a\x82": 2, # File not found
        None: 3,
    }
    SHORT_SW_WIDTH = 2
    
    def detect_bac(card):
        "Check whether BAC is active and if yes what type of card-os (select not allowed, select allowed but read not allowed)"
        result = card.open_file("\x01\x01", 0x0c) # EF.DG1
        
        if result.sw == "\x90\x00":
            prefix = str(SHORT_SW_MAP[result.sw])
            result = card.send_apdu(utils.C_APDU(card.APDU_READ_BINARY, p1=0, p2=0, le=1))
        else:
            prefix = ""
        
        if SHORT_SW_MAP.has_key(result.sw):
            return prefix + str(SHORT_SW_MAP[result.sw]) 
        else:
            return prefix + "%s:%s" % (SHORT_SW_MAP[None], binascii.b2a_hex(result.sw) )
    
    def map_dg(card):
        "Get a map of which DGs exist and are readable/unreadable and with which SW they are unreadable"
        # Try to read 1 byte from each DG through READ BINARY with short file identifier
        responses = [card.send_apdu(utils.C_APDU(card.APDU_READ_BINARY, p1=i|0x80, p2=0, le=1)) for i in range(1,17)]
        
        result = []
        exceptional = []
        for response in responses:
            if SHORT_SW_MAP.has_key( response.sw ):
                result.append( SHORT_SW_MAP[response.sw] )
            else:
                result.append( SHORT_SW_MAP[None] )
                exceptional.append(response.sw)
        
        UNIT_FORMAT = "%X"
        UNIT_LEN = 4 # For hex in "%X" format. Would be 8 for hex in "%02X" format.
        compressed = []
        current = 0
        count = 0
        for r in result:
            if count >= UNIT_LEN:
                compressed.append( current )
                current = count = 0
            current = (current << SHORT_SW_WIDTH) | r
            count = count + SHORT_SW_WIDTH
        
        if count > 0:
            if not count >= UNIT_LEN:
                while count < UNIT_LEN:
                    current = current << SHORT_SW_WIDTH
                    count += SHORT_SW_WIDTH
            compressed.append( current )
            current = count = 0
        
        
        return "".join( [UNIT_FORMAT % r for r in compressed] ) + ":".join( (len(exceptional) > 0 and [""] or []) + [binascii.b2a_hex(e) for e in exceptional] )
    
    result = []
    postfix = ""
    test_icao = card.select_application(card.resolve_symbolic_aid("mrtd"), le=None)
    if test_icao.sw == "\x67\x00":
        postfix = ":6700" # SELECT APPLICATION with P2=0 and without Le returns 6700 Wrong Length
        test_icao = card.select_application(card.resolve_symbolic_aid("mrtd"), le=None, P2=0x0c)
    
    if not card.check_sw(test_icao.sw, card.PURPOSE_SUCCESS):
        result.append("N"+postfix) # Not an ICAO MRTD
    else:
        result.append("P"+postfix) # An ICAO MRTD
        
        bac = detect_bac(card)
        result.append(bac) # BAC status
        
        dgmap = map_dg(card)
        result.append(dgmap) # Data Group map
    
    return result
    
def fingerprint(card):
    def compress_atr(atr):
        numhist = ord(atr[1]) & 0x0f
        if binascii.a2b_hex( "3B8%X8001" % numhist ) == atr[:4]:
            # Contactless, conforming to PC/SC part 3 section 3.1.3.2.3
            
            if atr[4:6] == "\x80\x4f": # Status indicator in compact-tlv object
                si_len = ord(atr[6])
                aid = atr[7:7+si_len]
                
                if aid[:5] == "\xa0\x00\x00\x03\x06": # RID of PC/SC Workgroup
                    standard_and_name = aid[5:]
                    if standard_and_name[3:] == "\x00" * (len(standard_and_name)-3):
                        return "1:%s" % binascii.b2a_hex(standard_and_name[:3]) # RFU bytes unset
                    else:
                        return "2:%s" % binascii.b2a_hex(standard_and_name) # RFU bytes set
            
            return "0:%s" % binascii.b2a_hex(atr[4:])
        else:
            # Not contactless (or not conforming)
            return "3:%s" % binascii.b2a_hex(atr)
        return ""
    
    result = []
    
    atr = card.get_atr()
    try:
        catr = compress_atr(atr)
    except (KeyboardInterrupt, SystemExit):
        raise
    except: # Any error in the ATR processing
        catr = "F:%s" % binascii.b2a_hex(atr)
    result.append( catr )
    result.extend( fingerprint_7816(card) )
    result.extend( fingerprint_rfid(card) )
    
    return ",".join(result)
    
def match_fingerprint(fingerprint, database="fingerprints.txt"):
    fp = file(database, "r")
    
    results = []
    current_result = []
    first_line = True
    matched = False
    
    def do_match(line, fingerprint):
        return re.match(line.strip(), fingerprint.strip()) is not None
    
    for line in fp.readlines():
        if line[0] == "#":
            continue
        
        if line.strip() == "":
            matched = False
            if len(current_result) > 0:
                results.append(current_result)
                current_result = []
        elif not line[0].isspace():
            if do_match(line, fingerprint):
                matched = True
            else:
                matched = False
        elif matched:
            current_result.append(line.strip())
    
    if len(current_result) > 0:
        results.append(current_result)
        current_result = []
    
    fp.close()
    return ["\n".join(e) for e in results]
    
if __name__ == "__main__":
    c = readers.CommandLineArgumentHelper()
    (options, arguments) = c.getopt(sys.argv[1:])
    
    card_object = c.connect()
    card = cards.new_card_object(card_object)
    cards.generic_card.DEBUG = False
    
    print >>sys.stderr, "Using %s" % card.DRIVER_NAME
    
    if isinstance(card, cards.rfid_card.RFID_Card):
        print "UID: %s" % utils.hexdump(card.get_uid(), short=True)
    fp = fingerprint(card)
    print "Fingerprint: %s" % fp
    matches = match_fingerprint(fp)
    if len(matches) > 1:
        print "Matched as: \n\t+ %s" % "\nor\t+ ".join( ["\n\t  ".join(e.split("\n")) for e in matches] )
    elif len(matches) == 1:
        if len(matches[0].split("\n")) == 1:
            print "Matched as: %s" % matches[0]
        else:
            print "Matched as: \n\t%s" % "\n\t".join( matches[0].split("\n") )