-
Notifications
You must be signed in to change notification settings - Fork 5
/
hccard.py
170 lines (142 loc) · 5.43 KB
/
hccard.py
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
# This file is part of twnhi-smartcard-agent.
#
# twnhi-smartcard-agent 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 3 of the License, or
# (at your option) any later version.
#
# twnhi-smartcard-agent 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.
#
# You should have received a copy of the GNU General Public License
# along with twnhi-smartcard-agent.
# If not, see <https://www.gnu.org/licenses/>.
#!/usr/bin/env python3
import logging
import sys
from collections import namedtuple
from smartcard.System import readers as get_readers
from smartcard.util import toHexString
logging.basicConfig(level='INFO', stream=sys.stdout)
logger = logging.getLogger(__name__)
class SmartcardException(Exception):
pass
class SmartcardCommandException(SmartcardException):
def __init__(self, *args):
super.__init__(*args)
self.error_code = None
self.description = None
class SmartcardClient:
def __init__(self, conn=None):
if conn is None:
conn = select_reader_and_connect()
if not conn:
raise SmartcardException('Smartcard connection was not provided')
self.conn = conn
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, traceback):
self.close()
def close(self):
if self.conn:
self.conn.disconnect()
def fire(self, cmd):
data, a, b = self.conn.transmit(cmd)
if (a, b) != (0x90, 0x00):
raise SmartcardCommandException(data, (a, b))
return bytes(data)
HCBasicData = namedtuple('HCBaseData', ['card_id', 'id', 'name', 'birth', 'gender', 'unknown'])
def error_info(error_code, description):
def error_wrapper(f):
def wrapper(*args, **kwargs):
try:
return f(*args, **kwargs)
except SmartcardCommandException as e:
e.error_code = error_code
e.description = description
raise
return wrapper
return error_wrapper
class HealthInsuranceSmartcardClient(SmartcardClient):
@error_info(7004, 'Failed to select applet')
def select_applet(self):
logger.debug('select default applet')
self.fire([
0x00, 0xA4, 0x04, 0x00, 0x10, 0xD1, 0x58, 0x00,
0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x11, 0x00
])
def select_sam_applet(self):
logger.debug('select sam applet')
self.fire([
0x00, 0xA4, 0x04, 0x00, 0x10, 0xD1, 0x58, 0x00,
0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x31, 0x00
])
@error_info(8011, 'Failed to get basic data')
def get_basic(self):
logger.debug('get basic data')
data = self.fire([0, 0xca, 0x11, 0, 2, 0, 0, 0])
return HCBasicData(
data[:12].decode('ascii'),
data[32:42].decode('ascii'),
data[12:32].rstrip(b'\0').decode('big5-hkscs'),
data[42:49].decode('ascii'),
data[49:50].decode('ascii'),
data[50:].decode('ascii'),
)
@error_info(8010, 'Failed to get card data')
def get_hc_card_data(self):
logger.debug('get HC card data')
return self.fire([0, 0xca, 0x24, 0, 2, 0, 0, 0])
@error_info(8001, 'Failed to get card id')
def get_hc_card_id(self):
logger.debug('get HC card id')
return self.fire([0, 0xca, 0, 0, 2, 0, 0, 0])
@error_info(8002, 'Failed to get card random')
def get_random(self):
logger.debug('get random')
return self.fire([0, 0x84, 0, 0, 8])
@error_info(8006, 'Secure access module signing failed')
def muauth_hc_dc_sam(self, data: bytes):
logger.debug('muauth_hc_dc_sam')
if len(data) > 32:
raise ValueError('data size must be less than 33 bytes')
prefix = [0x00, 0x82, 0x11, 0x12, 0x20]
suffix = [0x10]
payload = prefix + list(data.ljust(32, b'\0')) + suffix
assert len(payload) == 0x26
return self.fire(payload)
def select_reader_and_connect(interactive=False):
readers = get_readers()
if not readers:
logger.error('Please connect your smartcard reader')
return
elif len(readers) == 1:
logger.info('Only one reader connected, use that one: %s', readers[0])
reader = readers[0]
elif not interactive:
logger.info('Non-interactive was used, select first reader')
reader = readers[0]
else:
print('%d readers available, please select one:' % len(readers))
for i, r in enumerate(readers):
print('%-2d : %s' % (i, r))
idx = int(input('\n Reader number: '))
reader = readers[idx]
conn = reader.createConnection()
conn.connect()
return conn
if __name__ == '__main__':
try:
conn = select_reader_and_connect(True)
if not conn:
raise Exception('No reader connected or selection failed')
except Exception as e:
logger.exception('Can not connect to reader, error: %r', e)
sys.exit(1)
with HealthInsuranceSmartcardClient(conn) as client:
client.select_applet()
print(client.get_basic())