Skip to content

Commit 6c3c104

Browse files
remove default data, make hmac len 14 to avoid uscard crashes
1 parent 2a4bd42 commit 6c3c104

File tree

6 files changed

+197
-21
lines changed

6 files changed

+197
-21
lines changed

src/main/java/toys/MemoryCardApplet.java

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -37,17 +37,7 @@ public static void install(byte[] bArray, short bOffset, byte bLength){
3737
}
3838
public MemoryCardApplet(){
3939
super();
40-
41-
// Default data
42-
byte[] defaultData = {
43-
'M', 'e', 'm', 'o', 'r', 'y', ' ', 'c',
44-
'a', 'r', 'd', 's', ' ', 'a', 'r', 'e',
45-
' ', 'n', 'o', 't', ' ', 's', 'a', 'f',
46-
'u', ' ', 's', 'o', ' ', 'w', 'h', 'a',
47-
't', '?'
48-
};
4940
secretData = new DataEntry(MAX_DATA_LENGTH);
50-
secretData.put(defaultData, (short)0, (short)defaultData.length);
5141
}
5242
protected short processSecureMessage(byte[] buf, short len){
5343
if(buf[OFFSET_CMD] == CMD_STORAGE){

src/main/java/toys/SecureChannel.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public class SecureChannel{
2828
static final public short MAX_LENGTH_KEY = (short)32;
2929
/** Size of the HMAC code used in messages.
3030
* We reduce it to 15 bytes to increase data capacity. */
31-
static final public short LENGTH_MAC = (short)15;
31+
static final public short LENGTH_MAC = (short)14;
3232
/** Size of the IV for AES */
3333
static final public short LENGTH_IV = (short)16;
3434
/** Maximum size of the cyphertext including MAC */

tests/tests/test_memorycard.py

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
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\x00ping")
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()

tests/tests/test_secureapplet.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
AID = "B00B5111FF01"
66
APPLET = "toys.SecureApplet"
7-
CLASSDIR = "SecureApplet"
7+
CLASSDIR = "Secure"
88

99
mode = os.environ.get('TEST_MODE', "simulator")
1010
if mode=="simulator":

tests/tests/util/securechannel.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ def __init__(self, code):
1111
self.code = code
1212

1313
class SecureChannel:
14+
HMAC_LEN = 14
1415
def __init__(self, card):
1516
"""Pass Card or Simulator instance here"""
1617
self.card = card
@@ -59,10 +60,10 @@ def open(self, mode=None):
5960
secp256k1.ec_pubkey_tweak_mul(pub, secret)
6061
shared_secret = hashlib.sha256(secp256k1.ec_pubkey_serialize(pub)[1:33]).digest()
6162
shared_fingerprint = self.derive_keys(shared_secret)
62-
recv_hmac = s.read(15)
63+
recv_hmac = s.read(self.HMAC_LEN)
6364
h = hmac.new(self.card_mac_key, digestmod='sha256')
6465
h.update(data)
65-
expected_hmac = h.digest()[:15]
66+
expected_hmac = h.digest()[:self.HMAC_LEN]
6667
if expected_hmac != recv_hmac:
6768
raise RuntimeError("Wrong HMAC. Got %s, expected %s" % (recv_hmac.hex(),expected_hmac.hex()))
6869
data += recv_hmac
@@ -82,13 +83,13 @@ def open(self, mode=None):
8283
res = self.card.request(b"\xB0\xB4\x00\x00"+encode(data))
8384
s = BytesIO(res)
8485
nonce_card = s.read(32)
85-
recv_hmac = s.read(15)
86+
recv_hmac = s.read(self.HMAC_LEN)
8687
secret_with_nonces = hashlib.sha256(shared_secret+nonce_card).digest()
8788
shared_fingerprint = self.derive_keys(secret_with_nonces)
8889
data = nonce_card
8990
h = hmac.new(self.card_mac_key, digestmod='sha256')
9091
h.update(data)
91-
expected_hmac = h.digest()[:15]
92+
expected_hmac = h.digest()[:self.HMAC_LEN]
9293
if expected_hmac != recv_hmac:
9394
raise RuntimeError("Wrong HMAC. Got %s, expected %s"%(recv_hmac.hex(),expected_hmac.hex()))
9495
data += recv_hmac
@@ -114,17 +115,17 @@ def encrypt(self, data):
114115
h = hmac.new(self.host_mac_key, digestmod='sha256')
115116
h.update(iv)
116117
h.update(ct)
117-
ct += h.digest()[:15]
118+
ct += h.digest()[:self.HMAC_LEN]
118119
return ct
119120

120121
def decrypt(self, ct):
121-
recv_hmac = ct[-15:]
122-
ct = ct[:-15]
122+
recv_hmac = ct[-self.HMAC_LEN:]
123+
ct = ct[:-self.HMAC_LEN]
123124
iv = self.iv.to_bytes(16, 'big')
124125
h = hmac.new(self.card_mac_key, digestmod='sha256')
125126
h.update(iv)
126127
h.update(ct)
127-
expected_hmac = h.digest()[:15]
128+
expected_hmac = h.digest()[:self.HMAC_LEN]
128129
if expected_hmac != recv_hmac:
129130
raise RuntimeError("Wrong HMAC. Got %s, expected %s"%(recv_hmac.hex(),expected_hmac.hex()))
130131
backend = default_backend()

tests/tests/util/simulator.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,4 +54,5 @@ def request(self, apdu):
5454

5555
def disconnect(self):
5656
self.s.close()
57-
self.proc.kill()
57+
self.proc.kill()
58+
time.sleep(1)

0 commit comments

Comments
 (0)