1+ #!/usr/bin/env python3
2+ import unittest , os
3+ from util .securechannel import SecureChannel , SecureError
4+
5+ AID = "B00B5111CB01"
6+ APPLET = "toys.MemoryCardApplet"
7+ CLASSDIR = "MemoryCard"
8+
9+ mode = os .environ .get ('TEST_MODE' , "simulator" )
10+ if mode == "simulator" :
11+ from util .simulator import Simulator , ISOException
12+ sim = Simulator (AID , APPLET , CLASSDIR )
13+ elif mode == "card" :
14+ from util .card import Card , ISOException
15+ sim = Card (AID )
16+ else :
17+ raise RuntimeError ("Not supported" )
18+
19+ def setUpModule ():
20+ sim .connect ()
21+
22+ def tearDownModule ():
23+ sim .disconnect ()
24+
25+ SELECT = b"\x00 \xA4 \x04 \x00 "
26+ GET_RANDOM = b"\xB0 \xB1 \x00 \x00 "
27+ GET_PUBKEY = b"\xB0 \xB2 \x00 \x00 "
28+
29+ def encode (data ):
30+ return bytes ([len (data )])+ data
31+
32+ class SecureAppletTest (unittest .TestCase ):
33+ def __init__ (self , * args , ** kwargs ):
34+ super ().__init__ (* args , ** kwargs )
35+
36+ def get_secure_channel (self , open = True ):
37+ sc = SecureChannel (sim )
38+ sc .open ()
39+ self .assertEqual (sc .is_open , True )
40+ return sc
41+
42+ def test_select (self ):
43+ # selecting applet
44+ data = SELECT + encode (bytes .fromhex (AID ))
45+ res = sim .request (data )
46+ self .assertEqual (res , b"" )
47+
48+ def test_random (self ):
49+ # test default value
50+ d1 = sim .request (GET_RANDOM )
51+ d2 = sim .request (GET_RANDOM )
52+ self .assertNotEqual (d1 , d2 )
53+ self .assertEqual (len (d1 ), 32 )
54+
55+ def test_pubkey (self ):
56+ pub = sim .request (GET_PUBKEY )
57+ self .assertEqual (pub [0 ], 0x04 )
58+ self .assertEqual (len (pub ), 65 )
59+
60+ def test_sc (self ):
61+ sc = self .get_secure_channel ()
62+ # echo
63+ data = sc .request (b"\x00 \x00 ping" )
64+ self .assertEqual (data , b"ping" )
65+ # secure random
66+ d1 = sc .request (b"\x01 \x00 " )
67+ d2 = sc .request (b"\x01 \x00 " )
68+ self .assertNotEqual (d1 , d2 )
69+ self .assertEqual (len (d1 ), 32 )
70+ # close channel
71+ sc .close ()
72+ self .assertEqual (sc .is_open , False )
73+
74+ def test_duplicate_sc (self ):
75+ # open one channel
76+ sc1 = self .get_secure_channel ()
77+ # open another channel
78+ sc2 = self .get_secure_channel ()
79+ # first should be invalid now
80+ with self .assertRaises (ISOException ) as e :
81+ # trying to get random data
82+ # with outdated channel
83+ sc1 .request (b"\x01 \x00 " )
84+ sc2 .close ()
85+
86+ def test_pin (self ):
87+ sc = self .get_secure_channel ()
88+ status = sc .request (b'\x03 \x00 ' )
89+ left , total , is_set = list (status )
90+ self .assertEqual (left , 10 )
91+ self .assertEqual (total , left )
92+ self .assertEqual (is_set , 0 )
93+ # if PIN is not set - check should raise
94+ with self .assertRaises (SecureError ) as e :
95+ sc .request (b'\x03 \x01 ' + b'q' )
96+ # set PIN
97+ pin = b"My PIN code"
98+ sc .request (b"\x03 \x04 " + pin )
99+ # check if it's set and card is unlocked now
100+ status = sc .request (b'\x03 \x00 ' )
101+ left , total , is_set = list (status )
102+ self .assertEqual (left , 10 )
103+ self .assertEqual (total , left )
104+ self .assertEqual (is_set , 2 )
105+ # lock the card
106+ sc .request (b"\x03 \x02 " )
107+ # check it's locked
108+ status = sc .request (b'\x03 \x00 ' )
109+ left , total , is_set = list (status )
110+ self .assertEqual (left , 10 )
111+ self .assertEqual (total , left )
112+ self .assertEqual (is_set , 1 )
113+ # check we can't unlock with wrong PIN
114+ with self .assertRaises (SecureError ) as e :
115+ sc .request (b'\x03 \x01 ' + pin + b'q' )
116+ # check that we have 9 attempts now
117+ status = sc .request (b'\x03 \x00 ' )
118+ left , total , is_set = list (status )
119+ self .assertEqual (left , 9 )
120+ self .assertEqual (total , 10 )
121+ self .assertEqual (is_set , 1 )
122+ # check we can unlock with valid PIN
123+ sc .request (b'\x03 \x01 ' + pin )
124+ status = sc .request (b'\x03 \x00 ' )
125+ left , total , is_set = list (status )
126+ # number of attempts should be 10 again
127+ self .assertEqual (left , 10 )
128+ self .assertEqual (total , 10 )
129+ self .assertEqual (is_set , 2 )
130+
131+ # check we can change PIN with valid PIN
132+ pin2 = b"qqq"
133+ sc .request (b'\x03 \x03 ' + encode (pin )+ encode (pin2 ))
134+ status = sc .request (b'\x03 \x00 ' )
135+ left , total , is_set = list (status )
136+ self .assertEqual (left , 10 )
137+ self .assertEqual (total , 10 )
138+ self .assertEqual (is_set , 2 )
139+ pin = pin2
140+ # check we can't change pin with invalid pin
141+ with self .assertRaises (SecureError ) as e :
142+ sc .request (b'\x03 \x03 ' + encode (pin2 + b"q" )+ encode (pin2 ))
143+ # card should be locked now
144+ status = sc .request (b'\x03 \x00 ' )
145+ left , total , is_set = list (status )
146+ self .assertEqual (left , 9 )
147+ self .assertEqual (total , 10 )
148+ self .assertEqual (is_set , 1 )
149+ # check we can't unset PIN with invalid PIN
150+ with self .assertRaises (SecureError ) as e :
151+ sc .request (b'\x03 \x05 ' + pin + b'q' )
152+ # card should be locked now
153+ status = sc .request (b'\x03 \x00 ' )
154+ left , total , is_set = list (status )
155+ self .assertEqual (left , 8 )
156+ self .assertEqual (total , 10 )
157+ self .assertEqual (is_set , 1 )
158+
159+ # get random should still work
160+ # even if the card is locked
161+ d = sc .request (b"\x01 \x00 " )
162+ self .assertEqual (len (d ), 32 )
163+
164+ # check we can unset with valid PIN
165+ sc .request (b'\x03 \x05 ' + pin )
166+ # should be unset now
167+ status = sc .request (b'\x03 \x00 ' )
168+ left , total , is_set = list (status )
169+ self .assertEqual (left , 10 )
170+ self .assertEqual (total , left )
171+ self .assertEqual (is_set , 0 )
172+ sc .close ()
173+
174+ def test_invalid (self ):
175+ with self .assertRaises (ISOException ) as e :
176+ # invalid INS
177+ sim .request (b"\xB0 \xA3 \x00 \x00 " )
178+
179+ with self .assertRaises (ISOException ) as e :
180+ # invalid CLA
181+ sim .request (b"\xB1 \xA1 \x00 \x00 " )
182+
183+ if __name__ == '__main__' :
184+ unittest .main ()
0 commit comments