blob: 2ca61656ee759f7b90eedfa6329ce02ae31fb574 [file] [log] [blame]
Sylvain Munaut76504e02010-12-07 00:24:32 +01001#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3
4""" pySim: various utilities
5"""
6
7#
8# Copyright (C) 2009-2010 Sylvain Munaut <tnt@246tNt.com>
9#
10# This program is free software: you can redistribute it and/or modify
11# it under the terms of the GNU General Public License as published by
12# the Free Software Foundation, either version 2 of the License, or
13# (at your option) any later version.
14#
15# This program is distributed in the hope that it will be useful,
16# but WITHOUT ANY WARRANTY; without even the implied warranty of
17# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18# GNU General Public License for more details.
19#
20# You should have received a copy of the GNU General Public License
21# along with this program. If not, see <http://www.gnu.org/licenses/>.
22#
23
24
25def h2b(s):
26 return ''.join([chr((int(x,16)<<4)+int(y,16)) for x,y in zip(s[0::2], s[1::2])])
27
28def b2h(s):
29 return ''.join(['%02x'%ord(x) for x in s])
30
31def h2i(s):
32 return [(int(x,16)<<4)+int(y,16) for x,y in zip(s[0::2], s[1::2])]
33
34def i2h(s):
35 return ''.join(['%02x'%(x) for x in s])
36
Alexander Chemerisa5f0ea62017-07-18 16:48:47 +030037def h2s(s):
Vadim Yanitskiyeb06b452020-05-10 02:32:46 +070038 return ''.join([chr((int(x,16)<<4)+int(y,16)) for x,y in zip(s[0::2], s[1::2])
39 if int(x + y, 16) != 0xff])
Alexander Chemerisa5f0ea62017-07-18 16:48:47 +030040
41def s2h(s):
42 return b2h(s)
43
Supreeth Herle7d77d2d2020-05-11 09:07:08 +020044# List of bytes to string
45def i2s(s):
46 return ''.join([chr(x) for x in s])
47
Sylvain Munaut76504e02010-12-07 00:24:32 +010048def swap_nibbles(s):
49 return ''.join([x+y for x,y in zip(s[1::2], s[0::2])])
50
51def rpad(s, l, c='f'):
52 return s + c * (l - len(s))
53
54def lpad(s, l, c='f'):
55 return c * (l - len(s)) + s
Alexander Chemeris5e96c3d2013-07-04 17:33:33 +040056
Ben Fox-Moore0ec14752018-09-24 15:47:02 +020057def half_round_up(n):
58 return (n + 1)//2
59
60# IMSI encoded format:
61# For IMSI 0123456789ABCDE:
62#
63# | byte 1 | 2 upper | 2 lower | 3 upper | 3 lower | ... | 9 upper | 9 lower |
64# | length in bytes | 0 | odd/even | 2 | 1 | ... | E | D |
65#
66# If the IMSI is less than 15 characters, it should be padded with 'f' from the end.
67#
68# The length is the total number of bytes used to encoded the IMSI. This includes the odd/even
69# parity bit. E.g. an IMSI of length 14 is 8 bytes long, not 7, as it uses bytes 2 to 9 to
70# encode itself.
71#
72# Because of this, an odd length IMSI fits exactly into len(imsi) + 1 // 2 bytes, whereas an
73# even length IMSI only uses half of the last byte.
74
Alexander Chemeris7be92ff2013-07-10 11:18:06 +040075def enc_imsi(imsi):
76 """Converts a string imsi into the value of the EF"""
Ben Fox-Moore0ec14752018-09-24 15:47:02 +020077 l = half_round_up(len(imsi) + 1) # Required bytes - include space for odd/even indicator
Alexander Chemeris7be92ff2013-07-10 11:18:06 +040078 oe = len(imsi) & 1 # Odd (1) / Even (0)
Ben Fox-Moore0ec14752018-09-24 15:47:02 +020079 ei = '%02x' % l + swap_nibbles('%01x%s' % ((oe<<3)|1, rpad(imsi, 15)))
Alexander Chemeris7be92ff2013-07-10 11:18:06 +040080 return ei
81
Alexander Chemeris5e96c3d2013-07-04 17:33:33 +040082def dec_imsi(ef):
83 """Converts an EF value to the imsi string representation"""
84 if len(ef) < 4:
85 return None
Pau Espin Pedrol665bd222017-12-29 20:30:35 +010086 l = int(ef[0:2], 16) * 2 # Length of the IMSI string
Ben Fox-Moore0ec14752018-09-24 15:47:02 +020087 l = l - 1 # Encoded length byte includes oe nibble
88 swapped = swap_nibbles(ef[2:]).rstrip('f')
Philipp Maiercd3d6262020-05-11 21:41:56 +020089 if len(swapped) < 1:
90 return None
Alexander Chemeris5e96c3d2013-07-04 17:33:33 +040091 oe = (int(swapped[0])>>3) & 1 # Odd (1) / Even (0)
Ben Fox-Moore0ec14752018-09-24 15:47:02 +020092 if not oe:
93 # if even, only half of last byte was used
Alexander Chemeris5e96c3d2013-07-04 17:33:33 +040094 l = l-1
Ben Fox-Moore0ec14752018-09-24 15:47:02 +020095 if l != len(swapped) - 1:
Alexander Chemeris5e96c3d2013-07-04 17:33:33 +040096 return None
Ben Fox-Moore0ec14752018-09-24 15:47:02 +020097 imsi = swapped[1:]
Alexander Chemeris5e96c3d2013-07-04 17:33:33 +040098 return imsi
99
100def dec_iccid(ef):
101 return swap_nibbles(ef).strip('f')
Alexander Chemeris7be92ff2013-07-10 11:18:06 +0400102
103def enc_iccid(iccid):
104 return swap_nibbles(rpad(iccid, 20))
105
106def enc_plmn(mcc, mnc):
Alexander Chemerisdddbf522017-07-18 16:49:59 +0300107 """Converts integer MCC/MNC into 3 bytes for EF"""
Harald Welte7f1d3c42020-05-12 21:12:44 +0200108 if len(mnc) == 2:
109 mnc = "F%s" % mnc
110 return swap_nibbles("%s%s" % (mcc, mnc))
Alexander Chemerisa5f0ea62017-07-18 16:48:47 +0300111
112def dec_spn(ef):
113 byte1 = int(ef[0:2])
114 hplmn_disp = (byte1&0x01 == 0x01)
115 oplmn_disp = (byte1&0x02 == 0x02)
116 name = h2s(ef[2:])
117 return (name, hplmn_disp, oplmn_disp)
118
119def enc_spn(name, hplmn_disp=False, oplmn_disp=False):
120 byte1 = 0x00
121 if hplmn_disp: byte1 = byte1|0x01
122 if oplmn_disp: byte1 = byte1|0x02
123 return i2h([byte1])+s2h(name)
Alexander Chemeris19fffa12018-01-11 13:06:43 +0900124
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100125def hexstr_to_fivebytearr(s):
126 return [s[i:i+10] for i in range(0, len(s), 10) ]
127
128# Accepts hex string representing three bytes
129def dec_mcc_from_plmn(plmn):
130 ia = h2i(plmn)
131 digit1 = ia[0] & 0x0F # 1st byte, LSB
132 digit2 = (ia[0] & 0xF0) >> 4 # 1st byte, MSB
133 digit3 = ia[1] & 0x0F # 2nd byte, LSB
134 if digit3 == 0xF and digit2 == 0xF and digit1 == 0xF:
135 return 0xFFF # 4095
Supreeth Herled24f1632019-11-30 10:37:09 +0100136 return derive_mcc(digit1, digit2, digit3)
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100137
138def dec_mnc_from_plmn(plmn):
139 ia = h2i(plmn)
140 digit1 = ia[2] & 0x0F # 3rd byte, LSB
141 digit2 = (ia[2] & 0xF0) >> 4 # 3rd byte, MSB
142 digit3 = (ia[1] & 0xF0) >> 4 # 2nd byte, MSB
143 if digit3 == 0xF and digit2 == 0xF and digit1 == 0xF:
144 return 0xFFF # 4095
Supreeth Herled24f1632019-11-30 10:37:09 +0100145 return derive_mnc(digit1, digit2, digit3)
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100146
147def dec_act(twohexbytes):
148 act_list = [
149 {'bit': 15, 'name': "UTRAN"},
150 {'bit': 14, 'name': "E-UTRAN"},
151 {'bit': 7, 'name': "GSM"},
152 {'bit': 6, 'name': "GSM COMPACT"},
153 {'bit': 5, 'name': "cdma2000 HRPD"},
154 {'bit': 4, 'name': "cdma2000 1xRTT"},
155 ]
156 ia = h2i(twohexbytes)
157 u16t = (ia[0] << 8)|ia[1]
158 sel = []
159 for a in act_list:
160 if u16t & (1 << a['bit']):
161 sel.append(a['name'])
162 return sel
163
164def dec_xplmn_w_act(fivehexbytes):
165 res = {'mcc': 0, 'mnc': 0, 'act': []}
166 plmn_chars = 6
167 act_chars = 4
168 plmn_str = fivehexbytes[:plmn_chars] # first three bytes (six ascii hex chars)
169 act_str = fivehexbytes[plmn_chars:plmn_chars + act_chars] # two bytes after first three bytes
170 res['mcc'] = dec_mcc_from_plmn(plmn_str)
171 res['mnc'] = dec_mnc_from_plmn(plmn_str)
172 res['act'] = dec_act(act_str)
173 return res
174
175def format_xplmn_w_act(hexstr):
176 s = ""
177 for rec_data in hexstr_to_fivebytearr(hexstr):
178 rec_info = dec_xplmn_w_act(rec_data)
179 if rec_info['mcc'] == 0xFFF and rec_info['mnc'] == 0xFFF:
180 rec_str = "unused"
181 else:
Supreeth Herled24f1632019-11-30 10:37:09 +0100182 rec_str = "MCC: %03d MNC: %03d AcT: %s" % (rec_info['mcc'], rec_info['mnc'], ", ".join(rec_info['act']))
Daniel Laszlo Sitzer851e9c02018-12-04 19:40:08 +0100183 s += "\t%s # %s\n" % (rec_data, rec_str)
184 return s
185
Sebastian Vivianie61170c2020-06-03 08:57:00 +0100186def dec_loci(hexstr):
187 res = {'tmsi': '', 'mcc': 0, 'mnc': 0, 'lac': '', 'status': 0}
188 res['tmsi'] = hexstr[:8]
189 res['mcc'] = dec_mcc_from_plmn(hexstr[8:14])
190 res['mnc'] = dec_mnc_from_plmn(hexstr[8:14])
191 res['lac'] = hexstr[14:18]
192 res['status'] = h2i(hexstr[20:22])
193 return res
194
195def dec_psloci(hexstr):
196 res = {'p-tmsi': '', 'p-tmsi-sig': '', 'mcc': 0, 'mnc': 0, 'lac': '', 'rac': '', 'status': 0}
197 res['p-tmsi'] = hexstr[:8]
198 res['p-tmsi-sig'] = hexstr[8:14]
199 res['mcc'] = dec_mcc_from_plmn(hexstr[14:20])
200 res['mnc'] = dec_mnc_from_plmn(hexstr[14:20])
201 res['lac'] = hexstr[20:24]
202 res['rac'] = hexstr[24:26]
203 res['status'] = h2i(hexstr[26:28])
204 return res
205
206def dec_epsloci(hexstr):
207 res = {'guti': '', 'mcc': 0, 'mnc': 0, 'tac': '', 'status': 0}
208 res['guti'] = hexstr[:24]
209 res['tai'] = hexstr[24:34]
210 res['mcc'] = dec_mcc_from_plmn(hexstr[24:30])
211 res['mnc'] = dec_mnc_from_plmn(hexstr[24:30])
212 res['tac'] = hexstr[30:34]
213 res['status'] = h2i(hexstr[34:36])
214 return res
215
Alexander Chemeris19fffa12018-01-11 13:06:43 +0900216def derive_milenage_opc(ki_hex, op_hex):
217 """
218 Run the milenage algorithm to calculate OPC from Ki and OP
219 """
220 from Crypto.Cipher import AES
221 from Crypto.Util.strxor import strxor
222 from pySim.utils import b2h
223
224 # We pass in hex string and now need to work on bytes
225 aes = AES.new(h2b(ki_hex))
226 opc_bytes = aes.encrypt(h2b(op_hex))
227 return b2h(strxor(opc_bytes, h2b(op_hex)))
228
229def calculate_luhn(cc):
230 """
231 Calculate Luhn checksum used in e.g. ICCID and IMEI
232 """
233 num = map(int, str(cc))
234 check_digit = 10 - sum(num[-2::-2] + [sum(divmod(d * 2, 10)) for d in num[::-2]]) % 10
235 return 0 if check_digit == 10 else check_digit
Philipp Maier7592eee2019-09-12 13:03:23 +0200236
237def mcc_from_imsi(imsi):
238 """
239 Derive the MCC (Mobile Country Code) from the first three digits of an IMSI
240 """
241 if imsi == None:
242 return None
243
244 if len(imsi) > 3:
245 return imsi[:3]
246 else:
247 return None
248
249def mnc_from_imsi(imsi, long=False):
250 """
251 Derive the MNC (Mobile Country Code) from the 4th to 6th digit of an IMSI
252 """
253 if imsi == None:
254 return None
255
256 if len(imsi) > 3:
257 if long:
258 return imsi[3:6]
259 else:
260 return imsi[3:5]
261 else:
262 return None
Supreeth Herled24f1632019-11-30 10:37:09 +0100263
264def derive_mcc(digit1, digit2, digit3):
265 """
266 Derive decimal representation of the MCC (Mobile Country Code)
267 from three given digits.
268 """
269
270 mcc = 0
271
272 if digit1 != 0x0f:
273 mcc += digit1 * 100
274 if digit2 != 0x0f:
275 mcc += digit2 * 10
276 if digit3 != 0x0f:
277 mcc += digit3
278
279 return mcc
280
281def derive_mnc(digit1, digit2, digit3=0x0f):
282 """
283 Derive decimal representation of the MNC (Mobile Network Code)
284 from two or (optionally) three given digits.
285 """
286
287 mnc = 0
288
289 # 3-rd digit is optional for the MNC. If present
290 # the algorythm is the same as for the MCC.
291 if digit3 != 0x0f:
292 return derive_mcc(digit1, digit2, digit3)
293
294 if digit1 != 0x0f:
295 mnc += digit1 * 10
296 if digit2 != 0x0f:
297 mnc += digit2
298
299 return mnc
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100300
301def dec_msisdn(ef_msisdn):
302 """
303 Decode MSISDN from EF.MSISDN or EF.ADN (same structure).
304 See 3GPP TS 31.102, section 4.2.26 and 4.4.2.3.
305 """
306
307 # Convert from str to (kind of) 'bytes'
308 ef_msisdn = h2b(ef_msisdn)
309
310 # Make sure mandatory fields are present
311 if len(ef_msisdn) < 14:
312 raise ValueError("EF.MSISDN is too short")
313
314 # Skip optional Alpha Identifier
315 xlen = len(ef_msisdn) - 14
316 msisdn_lhv = ef_msisdn[xlen:]
317
318 # Parse the length (in bytes) of the BCD encoded number
319 bcd_len = ord(msisdn_lhv[0])
320 # BCD length = length of dial num (max. 10 bytes) + 1 byte ToN and NPI
321 if bcd_len == 0xff:
322 return None
323 elif bcd_len > 11 or bcd_len < 1:
324 raise ValueError("Length of MSISDN (%d bytes) is out of range" % bcd_len)
325
326 # Parse ToN / NPI
327 ton = (ord(msisdn_lhv[1]) >> 4) & 0x07
328 npi = ord(msisdn_lhv[1]) & 0x0f
329 bcd_len -= 1
330
331 # No MSISDN?
332 if not bcd_len:
333 return (npi, ton, None)
334
335 msisdn = swap_nibbles(b2h(msisdn_lhv[2:][:bcd_len])).rstrip('f')
336 # International number 10.5.118/3GPP TS 24.008
Vadim Yanitskiy7ba24282020-02-27 00:04:13 +0700337 if ton == 0x01:
Supreeth Herle4b1c7632019-12-22 09:00:59 +0100338 msisdn = '+' + msisdn
339
340 return (npi, ton, msisdn)
Supreeth Herle5a541012019-12-22 08:59:16 +0100341
342def enc_msisdn(msisdn, npi=0x01, ton=0x03):
343 """
344 Encode MSISDN as LHV so it can be stored to EF.MSISDN.
345 See 3GPP TS 31.102, section 4.2.26 and 4.4.2.3.
346
347 Default NPI / ToN values:
348 - NPI: ISDN / telephony numbering plan (E.164 / E.163),
349 - ToN: network specific or international number (if starts with '+').
350 """
351
352 # Leading '+' indicates International Number
353 if msisdn[0] == '+':
354 msisdn = msisdn[1:]
355 ton = 0x01
356
357 # Append 'f' padding if number of digits is odd
358 if len(msisdn) % 2 > 0:
359 msisdn += 'f'
360
361 # BCD length also includes NPI/ToN header
362 bcd_len = len(msisdn) // 2 + 1
363 npi_ton = (npi & 0x0f) | ((ton & 0x07) << 4) | 0x80
364 bcd = rpad(swap_nibbles(msisdn), 10 * 2) # pad to 10 octets
365
366 return ('%02x' % bcd_len) + ('%02x' % npi_ton) + bcd
Supreeth Herle441c4a72020-03-24 10:19:15 +0100367
Supreeth Herle0c4d82d2020-04-20 13:28:31 +0200368def dec_st(st, table="sim"):
369 """
370 Parses the EF S/U/IST and prints the list of available services in EF S/U/IST
371 """
372
Supreeth Herledf330372020-04-20 14:48:55 +0200373 if table == "isim":
374 from pySim.ts_31_103 import EF_IST_map
375 lookup_map = EF_IST_map
376 elif table == "usim":
Supreeth Herle0c4d82d2020-04-20 13:28:31 +0200377 from pySim.ts_31_102 import EF_UST_map
378 lookup_map = EF_UST_map
379 else:
380 from pySim.ts_51_011 import EF_SST_map
381 lookup_map = EF_SST_map
382
383 st_bytes = [st[i:i+2] for i in range(0, len(st), 2) ]
384
385 avail_st = ""
386 # Get each byte and check for available services
387 for i in range(0, len(st_bytes)):
388 # Byte i contains info about Services num (8i+1) to num (8i+8)
389 byte = int(st_bytes[i], 16)
390 # Services in each byte are in order MSB to LSB
391 # MSB - Service (8i+8)
392 # LSB - Service (8i+1)
393 for j in range(1, 9):
394 if byte&0x01 == 0x01 and ((8*i) + j in lookup_map):
395 # Byte X contains info about Services num (8X-7) to num (8X)
396 # bit = 1: service available
397 # bit = 0: service not available
398 avail_st += '\tService %d - %s\n' % ((8*i) + j, lookup_map[(8*i) + j])
399 byte = byte >> 1
400 return avail_st
Supreeth Herle98370552020-05-11 09:04:41 +0200401
402def first_TLV_parser(bytelist):
403 '''
404 first_TLV_parser([0xAA, 0x02, 0xAB, 0xCD, 0xFF, 0x00]) -> (170, 2, [171, 205])
405
406 parses first TLV format record in a list of bytelist
407 returns a 3-Tuple: Tag, Length, Value
408 Value is a list of bytes
409 parsing of length is ETSI'style 101.220
410 '''
411 Tag = bytelist[0]
412 if bytelist[1] == 0xFF:
413 Len = bytelist[2]*256 + bytelist[3]
414 Val = bytelist[4:4+Len]
415 else:
416 Len = bytelist[1]
417 Val = bytelist[2:2+Len]
418 return (Tag, Len, Val)
419
420def TLV_parser(bytelist):
421 '''
422 TLV_parser([0xAA, ..., 0xFF]) -> [(T, L, [V]), (T, L, [V]), ...]
423
424 loops on the input list of bytes with the "first_TLV_parser()" function
425 returns a list of 3-Tuples
426 '''
427 ret = []
428 while len(bytelist) > 0:
429 T, L, V = first_TLV_parser(bytelist)
430 if T == 0xFF:
431 # padding bytes
432 break
433 ret.append( (T, L, V) )
434 # need to manage length of L
435 if L > 0xFE:
436 bytelist = bytelist[ L+4 : ]
437 else:
438 bytelist = bytelist[ L+2 : ]
439 return ret
Supreeth Herled572ede2020-03-22 09:55:04 +0100440
441def dec_epdgid(hexstr):
442 """
443 Decode ePDG Id to get EF.ePDGId or EF.ePDGIdEm.
444 See 3GPP TS 31.102 version 13.4.0 Release 13, section 4.2.102 and 4.2.104.
445 """
446
447 # Convert from hex str to int bytes list
448 epdgid_bytes = h2i(hexstr)
449
450 s = ""
451
452 # Get list of tuples containing parsed TLVs
453 tlvs = TLV_parser(epdgid_bytes)
454
455 for tlv in tlvs:
456 # tlv = (T, L, [V])
457 # T = Tag
458 # L = Length
459 # [V] = List of value
460
461 # Invalid Tag value scenario
462 if tlv[0] != 0x80:
463 continue
464
465 # First byte in the value has the address type
466 addr_type = tlv[2][0]
467 # TODO: Support parsing of IPv4 and IPv6
468 if addr_type == 0x00: #FQDN
469 # Skip address tye byte i.e. first byte in value list
470 content = tlv[2][1:]
471 s += "\t%s # %s\n" % (i2h(content), i2s(content))
472
473 return s
Philipp Maierff84c232020-05-12 17:24:18 +0200474
Philipp Maiere8536c02020-05-11 21:35:01 +0200475def sanitize_pin_adm(opts):
476 """
477 The ADM pin can be supplied either in its hexadecimal form or as
478 ascii string. This function checks the supplied opts parameter and
479 returns the pin_adm as hex encoded string, regardles in which form
480 it was originally supplied by the user
481 """
482
483 pin_adm = None
484
485 if opts.pin_adm is not None:
486 if len(opts.pin_adm) <= 8:
487 pin_adm = ''.join(['%02x'%(ord(x)) for x in opts.pin_adm])
488 pin_adm = rpad(pin_adm, 16)
489
490 else:
491 raise ValueError("PIN-ADM needs to be <=8 digits (ascii)")
492
493 if opts.pin_adm_hex is not None:
494 if len(opts.pin_adm_hex) == 16:
495 pin_adm = opts.pin_adm_hex
496 # Ensure that it's hex-encoded
497 try:
498 try_encode = h2b(pin_adm)
499 except ValueError:
500 raise ValueError("PIN-ADM needs to be hex encoded using this option")
501 else:
502 raise ValueError("PIN-ADM needs to be exactly 16 digits (hex encoded)")
503
504 return pin_adm
505
Philipp Maierff84c232020-05-12 17:24:18 +0200506def init_reader(opts):
507 """
508 Init card reader driver
509 """
510 if opts.pcsc_dev is not None:
511 print("Using PC/SC reader interface")
512 from pySim.transport.pcsc import PcscSimLink
513 sl = PcscSimLink(opts.pcsc_dev)
514 elif opts.osmocon_sock is not None:
515 print("Using Calypso-based (OsmocomBB) reader interface")
516 from pySim.transport.calypso import CalypsoSimLink
517 sl = CalypsoSimLink(sock_path=opts.osmocon_sock)
Vadim Yanitskiy29ca8042020-05-09 21:23:37 +0700518 elif opts.modem_dev is not None:
519 print("Using modem for Generic SIM Access (3GPP TS 27.007)")
520 from pySim.transport.modem_atcmd import ModemATCommandLink
521 sl = ModemATCommandLink(device=opts.modem_dev, baudrate=opts.modem_baud)
Philipp Maierff84c232020-05-12 17:24:18 +0200522 else: # Serial reader is default
523 print("Using serial reader interface")
524 from pySim.transport.serial import SerialSimLink
525 sl = SerialSimLink(device=opts.device, baudrate=opts.baudrate)
526
527 return sl