Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit aa6e173

Browse files
committedSep 26, 2024·
lib/se05x: Add ISO7816, APDU and SE05x package.
Signed-off-by: iabdalkader <[email protected]>
1 parent 0f09935 commit aa6e173

File tree

6 files changed

+804
-0
lines changed

6 files changed

+804
-0
lines changed
 

‎lib/se05x/examples/sign_verify.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import logging
2+
import se05x
3+
from micropython import const
4+
5+
TC_R = "\033[91m"
6+
TC_G = "\033[92m"
7+
TC_B = "\033[94m"
8+
TC_RST = "\033[0m"
9+
KEY_ID = const(0x10)
10+
11+
if __name__ == "__main__":
12+
logging.basicConfig(
13+
datefmt="%H:%M:%S",
14+
format="%(asctime)s.%(msecs)03d %(message)s",
15+
level=logging.INFO
16+
)
17+
18+
# Create and initialize SE05x device.
19+
se = se05x.SE05X()
20+
21+
# Print applet version.
22+
major, minor, patch = se.version()
23+
logging.info(f"{TC_G}Applet Version: {major}.{minor}.{patch}{TC_RST}")
24+
25+
# Delete key object if it exists.
26+
if se.exists(KEY_ID):
27+
se.delete(KEY_ID)
28+
29+
# Generate EC key pair.
30+
se.write(KEY_ID, se05x.EC_KEY, curve=se05x.EC_CURVE_NIST_P256)
31+
ec_pub_key = se.read(KEY_ID)
32+
logging.info(f"{TC_B}Public Key: " + "".join("%02X" % b for b in ec_pub_key) + TC_RST)
33+
34+
# Sign and verify hash.
35+
data = bytes(range(32))
36+
sign = se.sign(KEY_ID, data)
37+
logging.info(f"{TC_B}EC signature: " + "".join("%02X" % b for b in sign) + TC_RST)
38+
if not se.verify(KEY_ID, data, sign):
39+
logging.info(f"{TC_R}EC signature verified failed!{TC_RST}")
40+
logging.info(f"{TC_G}EC signature verified successfully!{TC_RST}")
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import logging
2+
from micropython import const
3+
import se05x
4+
5+
TC_R = "\033[91m"
6+
TC_G = "\033[92m"
7+
TC_B = "\033[94m"
8+
TC_RST = "\033[0m"
9+
KEY_ID = const(0x10)
10+
CERT_ID = const(0x11)
11+
12+
PRIVATE_KEY = const(
13+
b"\xd0\xa2\x15\x85\x55\xf2\x13\xa5\x3a\xae\xd8\xe1\x22\xd6\x0f\xd3"
14+
b"\x94\x15\x3c\xa5\x56\x65\xf2\x38\xc8\xf1\xed\x3f\xe9\x29\xde\xb0"
15+
)
16+
17+
PUBLIC_KEY = const(
18+
b"\x04\xb0\x86\x11\x63\xf3\x8e\xb6\x64\xc5\x46\xd8\xc6\x7f\x17\xbf"
19+
b"\xbc\x68\x24\xf0\x07\x68\x37\xa9\x26\xc2\xbd\x2d\x48\xf8\xd6\x85"
20+
b"\x6e\xa9\x61\xf3\x88\x1a\x98\x5f\xd8\x50\x53\x32\x46\x7f\xe4\x24"
21+
b"\x4a\x94\x1f\x87\xc8\x53\xa4\x91\x2a\x09\x3f\x72\xdf\x44\xb6\x87"
22+
b"\x03"
23+
)
24+
25+
CERT = const(
26+
b"\x2a\x40\x42\x1e\xe9\x36\x80\xbb\xb5\xb0\xc3\x76\xed\x7f\xca\xf8"
27+
b"\xf3\x12\xeb\x67\xee\xc1\x2f\x7e\xb3\x1b\x48\x36\x6d\x16\xba\xa3"
28+
b"\x38\x29\x5b\x22\x52\xf9\x97\x2f\xc9\xbb\x67\x2b\xc3\xe0\x0a\x57"
29+
b"\xbe\x64\x12\x0a\x62\xc4\xe7\xa6\xfe\xfc\xae\xee\x39\x84\xb9\x50"
30+
b"\x9f\x6d\x36\x87\xc0\xf6\x21\xb4\xb7\xa9\xe9\x82\x11\x0b\x9a\x62"
31+
b"\x04\x9f\x4f\x93\xcb\x33\xf3\x84\x95\x1e\x5f\xe8\x38\x93\xc4\xcb"
32+
b"\xe4\xf5\xd3\x61\x4d\x99\x14\xb2\xfb\xbe\xa5\xfe\xe8\x40\x09\xdd"
33+
b"\xc0\x89\xac\x27\x27\x2e\x52\x3b\x2f\x49\x09\xa5\xd3\x24\x3d\xb1"
34+
b"\x31\xee\x2f\xa6\x74\xb6\xb9\x1a\x4e\xd3\x48\x73\xff\x2e\x07\xc1"
35+
b"\x67\xf0\x75\x62\xd5\x7e\x80\x01\xe5\x24\xb4\x75\x37\x75\xf6\xfe"
36+
b"\x4e\x6c\xb2\xfc\xd1\xb4\x2c\x80\x72\x63\x50\x8d\x21\x86\xd7\x8b"
37+
b"\xf2\x75\xc2\xea\x49\x23\x95\x73\x7b\x91\x28\x69\x46\xd2\x00\x11"
38+
b"\x6c\x51\x65\xb4\xf9\x2b\x42\xe6\xeb\x8c\xa1\xd3\xb1\xec\xf1\x47"
39+
b"\x07\xe2\x24\x3d\x8c\xa5\xc3\x4f\xf5\xfd\x67\xb1\x45\x36\x6d\x30"
40+
b"\x0e\x89\x7d\x96\xe8\x3b\xeb\xae\x38\x1b\x07\xbd\xb4\xa8\xc1\x62"
41+
b"\xee\xa2\x78\x9f\xc5\x8f\xc0\x8b\x21\xdc\x55\x8e\xf2\x14\x3e\x40"
42+
b"\x33\xfc\x11\xf8\xc5\x2d\xc8\x0e\x73\x88\x23\xc3\xd2\xf2\xde\xb5"
43+
b"\x69\x0c\xa4\xb5\xb4\x64\xd5\x0f\xbd\x2a\x18\x35\x08\x73\xaa\xc5"
44+
b"\x32\x5a\x8f\xbe\x31\x9f\xda\x8d\x09\x5e\xbe\xf0\x9f\x45\xbe\x5d"
45+
b"\x5b\x26\x57\xa8\x7a\xcd\x63\xa9\x1a\x27\x49\x0a\x4e\x45\x32\x14"
46+
b"\x00\xbb\x88\xea\x5a\x46\x88\x59\x79\x5b\x01\xc4\xd4\x0a\x08\x55"
47+
b"\x20\x58\xba\xb4\x74\xfd\x40\x6b\xf6\x56\x55\xbd\xca\x56\x25\xdf"
48+
b"\xb8\xbb\x9a\xb7\x76\xb1\xcb\x1c\x81\xfd\x0f\xde\x87\x6a\xcf\x1d"
49+
b"\x77\xa4\xa0\x6a\xbe\xf7\xd0\x60\x7d\xba\xb5\x1c\xf0\x62\x4d\xab"
50+
b"\x62\xf9\x36\xf4\x95\x76\x90\x79\xc0\x47\xfc\x3e\x32\xc7\x60\x3a"
51+
b"\x31\xf4\xff\x86\xfc\x67\xb3\xa8\x32\x39\xce\x7c"
52+
)
53+
54+
55+
if __name__ == "__main__":
56+
logging.basicConfig(
57+
datefmt="%H:%M:%S",
58+
format="%(asctime)s.%(msecs)03d %(message)s",
59+
level=logging.INFO
60+
)
61+
62+
# Create and initialize SE05x device.
63+
se = se05x.SE05X()
64+
65+
# Print applet version.
66+
major, minor, patch = se.version()
67+
logging.info(f"{TC_G}Applet Version: {major}.{minor}.{patch}{TC_RST}")
68+
69+
# Delete key object if it exists.
70+
if se.exists(KEY_ID):
71+
se.delete(KEY_ID)
72+
73+
# Write public/private key pair and verify.
74+
se.write(KEY_ID, se05x.EC_KEY, key=(PRIVATE_KEY, PUBLIC_KEY), curve=se05x.EC_CURVE_NIST_P256)
75+
76+
# Read back the public key part and verify it.
77+
ec_pub_key = se.read(KEY_ID)
78+
if PUBLIC_KEY != ec_pub_key:
79+
logging.info(f"{TC_R}Key verification failed!{TC_RST}")
80+
logging.info(f"{TC_G}Key verified successfully!{TC_RST}")
81+
logging.info(f"{TC_B}Public Key: " + "".join("%02X" % b for b in ec_pub_key) + TC_RST)
82+
83+
# Delete certificate object if it exists.
84+
if se.exists(CERT_ID):
85+
se.delete(CERT_ID)
86+
87+
# Write binary certificate object.
88+
se.write(CERT_ID, se05x.BINARY, binary=CERT)
89+
logging.info(f"{TC_G}Binary certificate written successfully!{TC_RST}")
90+
91+
# Read back the certificate and verify it.
92+
if CERT != se.read(CERT_ID, 412):
93+
logging.info(f"{TC_R}Certificate verification failed!{TC_RST}")
94+
logging.info(f"{TC_G}Certificate verified successfully!{TC_RST}")

‎lib/se05x/manifest.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
metadata(
2+
description="ISO7816 standard implementation and SE05X driver for MicroPython.",
3+
version="0.0.1",
4+
)
5+
6+
package("se05x")

‎lib/se05x/se05x/__init__.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
from .se05x import SE05X
2+
from .iso7816 import SmartCard # noqa
3+
from micropython import const
4+
5+
# Secure Object Types.
6+
EC_KEY = const(0x01)
7+
AES_KEY = const(0x03)
8+
DES_KEY = const(0x04)
9+
HMAC_KEY = const(0x05)
10+
BINARY = const(0x06)
11+
USERID = const(0x07)
12+
CURVE = const(0x0B)
13+
SIGNATURE = const(0x0C)
14+
MAC = const(0x0D)
15+
CIPHER = const(0x0E)
16+
17+
# Supported EC curves.
18+
EC_CURVE_NIST_P192 = const(0x01)
19+
EC_CURVE_NIST_P224 = const(0x02)
20+
EC_CURVE_NIST_P256 = const(0x03)
21+
EC_CURVE_NIST_P384 = const(0x04)
22+
EC_CURVE_NIST_P521 = const(0x05)

‎lib/se05x/se05x/iso7816.py

Lines changed: 389 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,389 @@
1+
# This file is part of the se05x package.
2+
# Copyright (c) 2024 Arduino SA
3+
# This Source Code Form is subject to the terms of the Mozilla Public
4+
# License, v. 2.0. If a copy of the MPL was not distributed with this
5+
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
6+
#
7+
# An implementation of ISO7816-3/4 standards.
8+
import struct
9+
import logging
10+
from micropython import const
11+
from time import sleep
12+
13+
# PCB bits
14+
_NAD_OFFSET = const(0)
15+
_PCB_OFFSET = const(1)
16+
_LEN_OFFSET = const(2)
17+
_INF_OFFSET = const(3)
18+
19+
# Blocks
20+
_I_BLOCK = const(0x00)
21+
_I_BLOCK_N = const(6)
22+
_I_BLOCK_M = const(5)
23+
24+
_S_BLOCK = const(0xC0)
25+
_S_BLOCK_REQ = const(0x3F)
26+
27+
_R_BLOCK = const(0x80)
28+
_R_BLOCK_N = const(4)
29+
_R_BLOCK_ERR = const(0x03)
30+
31+
# S-Block request/response
32+
_RESYNC_REQ = const(0x00)
33+
_IFS_REQ = const(0x01)
34+
_ABORT_REQ = const(0x02)
35+
_WTX_REQ = const(0x03)
36+
_END_SESSION_REQ = const(0x05)
37+
_CHIP_RESET_REQ = const(0x06)
38+
_GET_ATR_REQ = const(0x07)
39+
_SOFT_RESET_REQ = const(0x0F)
40+
41+
_CLA_ISO7816 = const(0x00)
42+
_INS_GP_SELECT = const(0xA4)
43+
44+
_STATE_IBLK = const(0)
45+
_STATE_SBLK = const(1)
46+
_STATE_SEND = const(2)
47+
_STATE_RECV = const(3)
48+
_STATE_WAIT = const(4)
49+
_STATE_WWTX = const(5)
50+
_STATE_DONE = const(6)
51+
52+
53+
def log_enabled(level):
54+
return logging.getLogger().isEnabledFor(level)
55+
56+
57+
class SmartCard:
58+
def __init__(self, bus, nad, aid):
59+
self.seq = 0
60+
self.bus = bus
61+
self.sad = nad & 0xF0
62+
self.dad = nad & 0x0F
63+
self.aid = aid
64+
self.atr = None
65+
66+
# TODO: Optimize these
67+
self.w_buf = memoryview(bytearray(512))
68+
self.r_buf = memoryview(bytearray(512))
69+
self.s_buf = memoryview(bytearray(32))
70+
self.apdu_buf = memoryview(bytearray(512))
71+
72+
self.block_name = {
73+
_I_BLOCK: "I-Block",
74+
_R_BLOCK: "R-Block",
75+
_S_BLOCK: "S-Block"
76+
}
77+
self.state_name = {
78+
_STATE_IBLK: "IBLK",
79+
_STATE_SBLK: "SBLK",
80+
_STATE_SEND: "SEND",
81+
_STATE_RECV: "RECV",
82+
_STATE_WAIT: "WAIT",
83+
_STATE_WWTX: "WWTX",
84+
_STATE_DONE: "DONE",
85+
}
86+
self.apdu_status = {
87+
0x6700: "Wrong length",
88+
0x6985: "Conditions not satisfied",
89+
0x6982: "Security status not satisfied",
90+
0x6A80: "Wrong data",
91+
0x6984: "Data invalid",
92+
0x6986: "Command not allowed",
93+
0x6A82: "File not found",
94+
0x6A84: "File full",
95+
0x6D00: "Invalid or not supported instruction code.",
96+
}
97+
98+
def _block_type(self, pcb):
99+
return _I_BLOCK if pcb & 0x80 == 0 else pcb & 0xC0
100+
101+
def _block_size(self, buf):
102+
# NAD, PCB, LEN, INF[LEN], CRC[2]
103+
return 3 + buf[_LEN_OFFSET] + 2
104+
105+
def _block_crc16(self, prologue, data, poly=0x8408, crc=0xFFFF):
106+
# Calculate prologue checksum
107+
for i in prologue:
108+
crc ^= i
109+
for bit in range(8):
110+
crc = (crc >> 1) ^ poly if crc & 0x1 else crc >> 1
111+
112+
# Calculate data checksum
113+
for i in data:
114+
crc ^= i
115+
for bit in range(8):
116+
crc = (crc >> 1) ^ poly if crc & 0x1 else crc >> 1
117+
crc ^= 0xFFFF
118+
return ((crc & 0xFF) << 8) | ((crc >> 8) & 0xFF)
119+
120+
def _block_write(self, buf, delay=0):
121+
size = self._block_size(buf)
122+
self.bus.write(buf[0:size])
123+
124+
def _block_print(self, txrx, *args):
125+
if len(args) == 1:
126+
buf = args[0]
127+
nad, pcb, bsize = buf[_NAD_OFFSET:_LEN_OFFSET + 1]
128+
crc = buf[_LEN_OFFSET + bsize + 1] << 8 | buf[_LEN_OFFSET + bsize + 2]
129+
else:
130+
nad, pcb, bsize, crc, buf = args
131+
btype = self._block_type(pcb)
132+
bname = self.block_name[btype]
133+
boffs = _INF_OFFSET if len(args) == 1 else 0
134+
seq = (pcb >> _I_BLOCK_N) & 1 if btype == _I_BLOCK else (pcb >> _R_BLOCK_N) & 1
135+
if log_enabled(logging.DEBUG):
136+
logging.debug(
137+
f"{'Tx' if txrx else 'Rx'}: {bname} NAD: 0x{nad:X} "
138+
f"PCB: 0x{pcb:X} LEN: {bsize} SEQ: {seq} CRC: 0x{crc:X}"
139+
)
140+
buf_hex = "".join(f"{b:02X}" for b in buf[boffs:boffs + bsize])
141+
logging.debug(f"RAW: {nad:02X}{pcb:02X}{bsize:02X}{buf_hex}{crc:04X}" )
142+
143+
def _block_new(self, buf, btype, **kwargs):
144+
data = kwargs.get("data", None)
145+
bsize = 0 if data is None else len(data)
146+
buf[_NAD_OFFSET] = self.sad | self.dad
147+
buf[_PCB_OFFSET] = btype
148+
buf[_LEN_OFFSET] = bsize
149+
if btype == _S_BLOCK:
150+
buf[_PCB_OFFSET] |= kwargs["request"]
151+
elif btype == _I_BLOCK:
152+
buf[_PCB_OFFSET] |= self.seq << _I_BLOCK_N
153+
buf[_PCB_OFFSET] |= kwargs["chained"] << _I_BLOCK_M
154+
elif btype == _R_BLOCK:
155+
buf[_PCB_OFFSET] |= self.seq << _R_BLOCK_N
156+
buf[_PCB_OFFSET] |= kwargs["error"] & _R_BLOCK_ERR
157+
if bsize:
158+
buf[_INF_OFFSET:_INF_OFFSET + bsize] = data
159+
# Calculate and set CRC
160+
crc = self._block_crc16(buf[0:_INF_OFFSET], buf[_INF_OFFSET:_INF_OFFSET + bsize])
161+
buf[_LEN_OFFSET + bsize + 1] = (crc >> 8) & 0xFF
162+
buf[_LEN_OFFSET + bsize + 2] = (crc >> 0) & 0xFF
163+
# Toggle I-Block sequence
164+
if btype == _I_BLOCK:
165+
self.seq = self.seq ^ 1
166+
return buf
167+
168+
def _send_block(self, btype, arg, retry=25, backoff=1.2):
169+
r_offs = 0
170+
w_offs = 0
171+
retry_delay = 1 / 1000
172+
next_state = _STATE_SBLK if btype == _S_BLOCK else _STATE_IBLK
173+
174+
while retry:
175+
if log_enabled(logging.DEBUG):
176+
logging.debug(f"STATE: {self.state_name[next_state]} retry: {retry}")
177+
if next_state == _STATE_SBLK:
178+
next_state = _STATE_SEND
179+
prev_state = _STATE_RECV
180+
block = self._block_new(self.w_buf, _S_BLOCK, request=arg)
181+
elif next_state == _STATE_IBLK:
182+
next_state = _STATE_SEND
183+
prev_state = _STATE_RECV
184+
remain = len(arg) - w_offs
185+
bsize = min(remain, self.atr["IFSC"])
186+
chained = int(remain > self.atr["IFSC"])
187+
block = self._block_new(self.w_buf, _I_BLOCK, chained=chained, data=arg[w_offs:w_offs + bsize])
188+
w_offs += bsize
189+
elif next_state == _STATE_SEND:
190+
try:
191+
self._block_write(block)
192+
next_state = prev_state
193+
if log_enabled(logging.DEBUG):
194+
self._block_print(True, block)
195+
except Exception:
196+
retry -= 1
197+
elif next_state == _STATE_RECV:
198+
try:
199+
# Read NAD, PCB, LEN, information (if present) and CRC.
200+
nad, pcb, bsize = self.bus.read(self.s_buf[0:3])
201+
if bsize:
202+
self.bus.read(self.r_buf[r_offs:r_offs + bsize])
203+
crc = int.from_bytes(self.bus.read(self.s_buf[3:5]), "big")
204+
except Exception:
205+
retry -= 1
206+
next_state = _STATE_WAIT
207+
prev_state = _STATE_RECV
208+
continue
209+
210+
# Check NAD and CRC.
211+
if (nad != (self.sad >> 4) | (self.dad << 4) or
212+
crc != self._block_crc16(self.s_buf[0:3], self.r_buf[r_offs:r_offs + bsize])):
213+
retry -= 1
214+
next_state = _STATE_SEND
215+
prev_state = _STATE_RECV
216+
block = self._block_new(self.s_buf, _R_BLOCK, error=1)
217+
continue
218+
219+
if log_enabled(logging.DEBUG):
220+
self._block_print(False, nad, pcb, bsize, crc, self.r_buf[r_offs:r_offs + bsize])
221+
222+
# Process block.
223+
btype = self._block_type(pcb)
224+
if btype == _R_BLOCK:
225+
# Retransmit last block if error, or continue block chain.
226+
next_state = _STATE_SEND if pcb & _R_BLOCK_ERR else _STATE_IBLK
227+
prev_state = _STATE_RECV
228+
continue
229+
230+
if btype == _I_BLOCK:
231+
# Acknowledge I-Block in block chain with R(N(R)).
232+
if pcb & (1 << _I_BLOCK_M):
233+
next_state = _STATE_SEND
234+
prev_state = _STATE_RECV
235+
block = self._block_new(self.s_buf, _R_BLOCK, error=0)
236+
else:
237+
next_state = _STATE_DONE
238+
# Add current I-Block INF size (could be 0).
239+
r_offs += bsize
240+
continue
241+
242+
if btype == _S_BLOCK:
243+
if pcb & _S_BLOCK_REQ == _RESYNC_REQ:
244+
# Respond to a resync request.
245+
self.seq = 0
246+
next_state = _STATE_SEND
247+
prev_state = _STATE_RECV
248+
block = self._block_new(self.s_buf, _S_BLOCK, request=_RESYNC_REQ & 0x20)
249+
elif pcb & _S_BLOCK_REQ == _WTX_REQ:
250+
# Respond to a WTX request.
251+
next_state = _STATE_SEND
252+
prev_state = _STATE_WWTX
253+
wtx_delay = self.r_buf[r_offs] * self.atr["BWT"] / 1000
254+
block = self._block_new(self.s_buf, _S_BLOCK, request=_WTX_REQ & 0x20)
255+
else:
256+
# Add current S-Block INF size (could be 0).
257+
r_offs += bsize
258+
next_state = _STATE_DONE
259+
continue
260+
elif next_state == _STATE_WWTX:
261+
sleep(wtx_delay)
262+
next_state = _STATE_RECV
263+
elif next_state == _STATE_WAIT:
264+
sleep(retry_delay)
265+
retry_delay *= backoff
266+
next_state = prev_state
267+
elif next_state == _STATE_DONE:
268+
return self.r_buf[0:r_offs]
269+
270+
if retry == 0:
271+
raise RuntimeError("_send_block failed")
272+
273+
def send_apdu(self, cla, ins, p1, p2, data=None, le=0):
274+
size = 4
275+
self.apdu_buf[0] = cla
276+
self.apdu_buf[1] = ins
277+
self.apdu_buf[2] = p1
278+
self.apdu_buf[3] = p2
279+
if data is not None:
280+
size = len(data)
281+
self.apdu_buf[4] = size
282+
self.apdu_buf[5:5 + size] = data
283+
self.apdu_buf[5 + size] = 0x00
284+
size += 5
285+
286+
# Send APDU in I-Block
287+
resp = self._send_block(_I_BLOCK, self.apdu_buf[0:size])
288+
289+
# Check response TPDU status
290+
status = int.from_bytes(resp[-2:], "big")
291+
if status != 0x9000:
292+
raise RuntimeError("APDU Error: " + self.apdu_status.get(status, f"Unknown 0x{status:X}"))
293+
294+
# Return data bytes, if any, or the status.
295+
if len(resp) == 2:
296+
return status
297+
return resp[2 + (0 if resp[1] <= 0x7F else resp[1] & 0x0F):-2]
298+
299+
def reset(self):
300+
self.seq = 0
301+
atr_raw = self._send_block(_S_BLOCK, _SOFT_RESET_REQ)
302+
if self.atr is None:
303+
self.atr = self._parse_atrs(atr_raw)
304+
if log_enabled(logging.INFO):
305+
self._dump_atrs(self.atr)
306+
# Select applet
307+
self.send_apdu(_CLA_ISO7816, _INS_GP_SELECT, 0x04, 0x00, self.aid, le=True)
308+
309+
def resync(self):
310+
self._send_block(_S_BLOCK, _RESYNC_REQ)
311+
self.seq = 0
312+
313+
def _parse_atrs(self, atr_bytes):
314+
atr = {}
315+
# PVER - 1 byte
316+
atr["PVER"] = atr_bytes[0]
317+
# VID - 5 bytes
318+
atr["VID"] = atr_bytes[1:6].hex().upper()
319+
320+
# Length of DLLP - 1 byte
321+
dllp_length = atr_bytes[6]
322+
atr["DLLP_LENGTH"] = dllp_length
323+
324+
# DLLP - Variable length (Decode using struct)
325+
dllp = atr_bytes[7:7 + dllp_length]
326+
atr["DLLP"] = dllp.hex().upper()
327+
if dllp_length >= 4:
328+
atr["BWT"], atr["IFSC"] = struct.unpack(">HH", dllp[:4])
329+
330+
# PLID - 1 byte
331+
atr["PLID"] = atr_bytes[7 + dllp_length]
332+
# Length of PLP - 1 byte
333+
plp_length = atr_bytes[8 + dllp_length]
334+
atr["PLP_LENGTH"] = plp_length
335+
336+
# PLP - Variable length (Decode using struct)
337+
plp = atr_bytes[9 + dllp_length:9 + dllp_length + plp_length]
338+
atr["PLP"] = plp.hex().upper()
339+
if plp_length >= 2:
340+
atr["MCF"] = struct.unpack(">H", plp[:2])[0]
341+
if plp_length >= 3:
342+
atr["CONFIGURATION"] = plp[2]
343+
if plp_length >= 4:
344+
atr["MPOT"] = plp[3]
345+
if plp_length >= 6:
346+
atr["SEGT"], atr["WUT"] = struct.unpack(">HH", plp[4:8])
347+
348+
# Length of HB - 1 byte
349+
hb_length = atr_bytes[9 + dllp_length + plp_length]
350+
atr["HB_LENGTH"] = hb_length
351+
# HB - Variable length
352+
hb = atr_bytes[10 + dllp_length + plp_length:10 + dllp_length + plp_length + hb_length]
353+
atr["HB"] = hb.hex().upper()
354+
return atr
355+
356+
def _dump_atrs(self, atr):
357+
logging.info(f"PVER (Protocol Version): {atr['PVER']}")
358+
logging.info(f"VID (Vendor ID): {atr['VID']}")
359+
logging.info(f"Length of DLLP: {atr['DLLP_LENGTH']}")
360+
361+
if "DLLP" in atr:
362+
logging.info(f"DLLP: {atr['DLLP']}")
363+
364+
if "BWT" in atr and "IFSC" in atr:
365+
logging.info(f"BWT (Block Waiting Time): {atr['BWT']} ms")
366+
logging.info(f"IFSC (Maximum Information Field Size): {atr['IFSC']} bytes")
367+
368+
logging.info(f"PLID (Physical Layer ID): {atr['PLID']}")
369+
logging.info(f"Length of PLP: {atr['PLP_LENGTH']}")
370+
371+
if "PLP" in atr:
372+
logging.info(f"PLP: {atr['PLP']}")
373+
374+
if "MCF" in atr:
375+
logging.info(f"MCF (Max I2C Clock Frequency): {atr['MCF']} kHz")
376+
377+
if "CONFIGURATION" in atr:
378+
logging.info(f"Configuration: {atr['CONFIGURATION']:#04x}")
379+
380+
if "MPOT" in atr:
381+
logging.info(f"MPOT (Minimum Polling Time): {atr['MPOT']} ms")
382+
383+
if "SEGT" in atr and "WUT" in atr:
384+
logging.info(f"SEGT (Secure Element Guard Time): {atr['SEGT']} µs")
385+
logging.info(f"WUT (Wake-Up Time): {atr['WUT']} µs")
386+
387+
logging.info(f"Length of HB (Historical Bytes): {atr['HB_LENGTH']}")
388+
if "HB" in atr:
389+
logging.info(f"HB (Historical Bytes): {atr['HB']}")

‎lib/se05x/se05x/se05x.py

Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
# This file is part of the se05x package.
2+
# Copyright (c) 2024 Arduino SA
3+
# This Source Code Form is subject to the terms of the Mozilla Public
4+
# License, v. 2.0. If a copy of the MPL was not distributed with this
5+
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
6+
#
7+
# NXP SE05x EdgeLock device driver.
8+
9+
import struct
10+
import logging
11+
from time import sleep_ms
12+
from machine import I2C
13+
from machine import Pin
14+
from .iso7816 import SmartCard
15+
from micropython import const
16+
17+
_RESULT_OK = const(1)
18+
_APPLET_NAD = const(0x5A)
19+
_APPLET_AID = const(b"\xa0\x00\x00\x03\x96\x54\x53\x00"
20+
b"\x00\x00\x01\x03\x00\x00\x00\x00")
21+
_CLA_KSE05X = const(0x80)
22+
23+
_INS_WRITE = const(0x01)
24+
_INS_READ = const(0x02)
25+
_INS_CRYPTO = const(0x03)
26+
_INS_MGMT = const(0x04)
27+
_INS_PROCESS = const(0x05)
28+
_INS_IMPORT_EXTERNAL = const(0x06)
29+
_INS_TRANSIENT = const(0x80)
30+
_INS_AUTH_OBJECT = const(0x40)
31+
_INS_ATTEST = const(0x20)
32+
33+
_P1_DEFAULT = const(0x00)
34+
_P1_EC = const(0x01)
35+
_P1_AES = const(0x03)
36+
_P1_DES = const(0x04)
37+
_P1_HMAC = const(0x05)
38+
_P1_BINARY = const(0x06)
39+
_P1_USERID = const(0x07)
40+
_P1_CURVE = const(0x0B)
41+
_P1_SIGNATURE = const(0x0C)
42+
_P1_MAC = const(0x0D)
43+
_P1_CIPHER = const(0x0E)
44+
_P1_KEY_PRIVATE = const(0x40)
45+
_P1_KEY_PUBLIC = const(0x20)
46+
47+
_P2_DEFAULT = const(0x00)
48+
_P2_GENERATE = const(0x03)
49+
_P2_CREATE = const(0x04)
50+
_P2_SIZE = const(0x07)
51+
_P2_SIGN = const(0x09)
52+
_P2_VERIFY = const(0x0A)
53+
_P2_SESSION_CREATE = const(0x1B)
54+
_P2_SESSION_CLOSE = const(0x1C)
55+
_P2_VERSION = const(0x20)
56+
_P2_LIST = const(0x25)
57+
_P2_EXIST = const(0x27)
58+
_P2_DELETE_OBJECT = const(0x28)
59+
_P2_SESSION_USERID = const(0x2C)
60+
_P2_DH = const(0x0F)
61+
_P2_ENCRYPT_ONESHOT = const(0x37)
62+
_P2_DECRYPT_ONESHOT = const(0x38)
63+
_P2_SCP = const(0x52)
64+
_P2_ONESHOT = const(0x0E)
65+
66+
_TLV_TAG1 = const(0x41)
67+
_TLV_TAG2 = const(0x42)
68+
_TLV_TAG3 = const(0x43)
69+
_TLV_TAG4 = const(0x44)
70+
_TLV_TAG5 = const(0x45)
71+
_TLV_TAG6 = const(0x46)
72+
_TLV_TAG7 = const(0x47)
73+
_TLV_TAG8 = const(0x48)
74+
_TLV_TAG9 = const(0x49)
75+
_TLV_TAG10 = const(0x4A)
76+
_TLV_TAG11 = const(0x4B)
77+
_TLV_TAG_SESSION_ID = const(0x10)
78+
_TLV_TAG_POLICY = const(0x11)
79+
_TLV_TAG_MAX_ATTEMPTS = const(0x12)
80+
_TLV_TAG_IMPORT_AUTH_DATA = const(0x13)
81+
_TLV_TAG_IMPORT_AUTH_KEY_ID = const(0x14)
82+
_TLV_TAG_POLICY_CHECK = const(0x15)
83+
84+
_SIG_ECDSA_SHA_1 = const(0x11)
85+
_SIG_ECDSA_SHA_224 = const(0x25)
86+
_SIG_ECDSA_SHA_256 = const(0x21)
87+
_SIG_ECDSA_SHA_384 = const(0x22)
88+
_SIG_ECDSA_SHA_512 = const(0x26)
89+
90+
91+
class I2CBus:
92+
def __init__(self, addr, freq):
93+
self.addr = addr
94+
self.bus = None
95+
# Scan the first 3 I2C buses
96+
for i in range(3):
97+
try:
98+
bus = I2C(i, freq=freq)
99+
if self.addr in bus.scan():
100+
logging.info(f"SE05x detected on bus: {i} addr: 0x{addr:02X}")
101+
self.bus = bus
102+
break
103+
except Exception:
104+
pass
105+
if self.bus is None:
106+
raise RuntimeError("Failed to detect SE05x on I2C bus")
107+
108+
def read(self, buf):
109+
self.bus.readfrom_into(self.addr, buf)
110+
return buf
111+
112+
def write(self, buf):
113+
self.bus.writeto(self.addr, buf)
114+
115+
116+
class SE05X:
117+
def __init__(self, addr=0x48, freq=400_000, rst=Pin("SE05X_EN", Pin.OUT_PP, Pin.PULL_UP)):
118+
self.rst = rst
119+
self.scard = None
120+
self.reset()
121+
self.bus = I2CBus(addr, freq)
122+
self.scard = SmartCard(self.bus, _APPLET_NAD, _APPLET_AID)
123+
self.scard.reset()
124+
self.ecdsa_algo = {
125+
160: _SIG_ECDSA_SHA_1,
126+
224: _SIG_ECDSA_SHA_224,
127+
256: _SIG_ECDSA_SHA_256,
128+
384: _SIG_ECDSA_SHA_384,
129+
512: _SIG_ECDSA_SHA_512,
130+
}
131+
self.tlv_offs = 0
132+
self.tlv_buf = memoryview(bytearray(254))
133+
134+
def _tlv_pack(self, fmt, *args):
135+
struct.pack_into(fmt, self.tlv_buf, self.tlv_offs, *args)
136+
self.tlv_offs += struct.calcsize(fmt)
137+
138+
def _tlv_flush(self):
139+
mv = self.tlv_buf[0:self.tlv_offs]
140+
self.tlv_offs = 0
141+
return mv
142+
143+
def _ecdsa_algo(self, hash_size):
144+
if hash_size * 8 not in self.ecdsa_algo:
145+
raise ValueError("Invalid SHA digest size")
146+
return self.ecdsa_algo[hash_size * 8]
147+
148+
def reset(self, reset_card=True):
149+
self.rst.low()
150+
sleep_ms(10)
151+
self.rst.high()
152+
sleep_ms(10)
153+
if self.scard is not None:
154+
self.scard.reset()
155+
156+
def version(self):
157+
resp = self.scard.send_apdu(_CLA_KSE05X, _INS_MGMT, _P1_DEFAULT, _P2_VERSION)
158+
major, minor, patch = struct.unpack(">BBB", resp)
159+
return major, minor, patch
160+
161+
def read(self, obj_id, size=0):
162+
if not self.exists(obj_id):
163+
raise RuntimeError(f"Object with id 0x{obj_id:X} doesn't exist.")
164+
if size == 0:
165+
self._tlv_pack(">BBI", _TLV_TAG1, 0x4, obj_id)
166+
resp = self.scard.send_apdu(_CLA_KSE05X, _INS_READ, _P1_DEFAULT, _P2_DEFAULT, self._tlv_flush())
167+
return bytes(resp)
168+
offset = 0
169+
buf = bytearray()
170+
maxblk = 254 - struct.calcsize("BBIBBHBBH")
171+
while size:
172+
bsize = min(size, maxblk)
173+
self._tlv_pack(">BBI", _TLV_TAG1, 0x4, obj_id)
174+
self._tlv_pack(">BBH", _TLV_TAG2, 0x2, offset)
175+
self._tlv_pack(">BBH", _TLV_TAG3, 0x2, bsize)
176+
resp = self.scard.send_apdu(_CLA_KSE05X, _INS_READ, _P1_DEFAULT, _P2_DEFAULT, self._tlv_flush())
177+
buf.extend(resp)
178+
offset += bsize
179+
size -= bsize
180+
return bytes(buf)
181+
182+
def write(self, obj_id, obj_type, **kwargs):
183+
if self.exists(obj_id):
184+
raise RuntimeError(f"Object with id 0x{obj_id:X} already exists.")
185+
186+
ins = _INS_WRITE | kwargs.get("ins_flags", 0)
187+
if obj_type == _P1_EC:
188+
p1 = _P1_EC
189+
key = kwargs.get("key", (None, None))
190+
curve_id = kwargs.get("curve", 0x3)
191+
self._tlv_pack(">BBI", _TLV_TAG1, 0x4, obj_id)
192+
self._tlv_pack(">BBB", _TLV_TAG2, 0x1, curve_id)
193+
if key[0] is not None:
194+
p1 |= _P1_KEY_PRIVATE
195+
self._tlv_pack(f"BB{len(key[0])}s", _TLV_TAG3, len(key[0]), key[0])
196+
if key[1] is not None:
197+
p1 |= _P1_KEY_PUBLIC
198+
self._tlv_pack(f"BB{len(key[1])}s", _TLV_TAG4, len(key[1]), key[1])
199+
if key[0] is None and key[1] is None:
200+
p1 |= _P1_KEY_PRIVATE | _P1_KEY_PUBLIC
201+
self.scard.send_apdu(_CLA_KSE05X, ins, p1, _P2_DEFAULT, self._tlv_flush())
202+
elif obj_type == _P1_BINARY:
203+
offset = 0
204+
binary = kwargs["binary"]
205+
maxblk = 254 - struct.calcsize("BBIBBHBBHBBH")
206+
remain = len(binary)
207+
while remain:
208+
bsize = min(remain, maxblk)
209+
data = binary[offset:offset + bsize]
210+
self._tlv_pack(">BBI", _TLV_TAG1, 0x4, obj_id)
211+
self._tlv_pack(">BBH", _TLV_TAG2, 0x2, offset)
212+
if offset == 0:
213+
self._tlv_pack(">BBH", _TLV_TAG3, 0x2, remain)
214+
self._tlv_pack(f">BBH{bsize}s", _TLV_TAG4, 0x82, bsize, data)
215+
self.scard.send_apdu(_CLA_KSE05X, ins, _P1_BINARY, _P2_DEFAULT, self._tlv_flush())
216+
offset += bsize
217+
remain -= bsize
218+
219+
def delete(self, obj_id):
220+
if not self.exists(obj_id):
221+
raise RuntimeError(f"Object with id 0x{obj_id:X} doesn't exist.")
222+
self._tlv_pack(">BBI", _TLV_TAG1, 0x4, obj_id)
223+
self.scard.send_apdu(_CLA_KSE05X, _INS_MGMT, _P1_DEFAULT, _P2_DELETE_OBJECT, self._tlv_flush())
224+
225+
def exists(self, obj_id):
226+
self._tlv_pack(">BBI", _TLV_TAG1, 0x4, obj_id)
227+
resp = self.scard.send_apdu(
228+
_CLA_KSE05X, _INS_MGMT, _P1_DEFAULT, _P2_EXIST, self._tlv_flush()
229+
)
230+
return resp[0] == _RESULT_OK
231+
232+
def sign(self, obj_id, data):
233+
if not self.exists(obj_id):
234+
raise RuntimeError(f"Object with id 0x{obj_id:X} doesn't exist.")
235+
hash_size = len(data)
236+
hash_algo = self._ecdsa_algo(hash_size)
237+
self._tlv_pack(">BBIBBB", _TLV_TAG1, 0x4, obj_id, _TLV_TAG2, 0x1, hash_algo)
238+
self._tlv_pack(f"BB{hash_size}s", _TLV_TAG3, hash_size, data)
239+
resp = self.scard.send_apdu(_CLA_KSE05X, _INS_CRYPTO, _P1_SIGNATURE, _P2_SIGN, self._tlv_flush())
240+
return bytes(resp)
241+
242+
def verify(self, obj_id, data, sign):
243+
if not self.exists(obj_id):
244+
raise RuntimeError(f"Object with id 0x{obj_id:X} doesn't exist.")
245+
hash_size = len(data)
246+
sign_size = len(sign)
247+
hash_algo = self._ecdsa_algo(hash_size)
248+
self._tlv_pack(">BBI", _TLV_TAG1, 0x4, obj_id)
249+
self._tlv_pack(">BBB", _TLV_TAG2, 0x1, hash_algo)
250+
self._tlv_pack(f"BB{hash_size}s", _TLV_TAG3, hash_size, data)
251+
self._tlv_pack(f"BB{sign_size}s", _TLV_TAG5, sign_size, sign)
252+
resp = self.scard.send_apdu(_CLA_KSE05X, _INS_CRYPTO, _P1_SIGNATURE, _P2_VERIFY, self._tlv_flush())
253+
return resp[0] == _RESULT_OK

0 commit comments

Comments
 (0)
Please sign in to comment.