mirror of
https://gitea.osmocom.org/sim-card/pysim.git
synced 2026-03-16 18:38:32 +03:00
javacard: add parser for JAVA-card CAP file format
To install JAVA-card applets we need to be able to extract the executeable loadfile and the AIDs of the applet and the loadfile. This patch adds the parser and related unittests. Related: OS#6679 Change-Id: I581483ccb9d8a254fcecc995fec3c811c5cf38eb
This commit is contained in:
@@ -1,9 +1,30 @@
|
|||||||
# JavaCard related utilities
|
# JavaCard related utilities
|
||||||
|
#
|
||||||
|
# (C) 2024 by Sysmocom s.f.m.c. GmbH
|
||||||
|
# All Rights Reserved
|
||||||
|
#
|
||||||
|
# This program 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 2 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
import zipfile
|
import zipfile
|
||||||
import struct
|
import struct
|
||||||
import sys
|
import sys
|
||||||
import io
|
import io
|
||||||
|
from osmocom.utils import b2h, Hexstr
|
||||||
|
from construct import Struct, Array, this, Int32ub, Int16ub, Int8ub
|
||||||
|
from osmocom.construct import *
|
||||||
|
from osmocom.tlv import *
|
||||||
|
from construct import Optional as COptional
|
||||||
|
|
||||||
def ijc_to_cap(in_file: io.IOBase, out_zip: zipfile.ZipFile, p : str = "foo"):
|
def ijc_to_cap(in_file: io.IOBase, out_zip: zipfile.ZipFile, p : str = "foo"):
|
||||||
"""Convert an ICJ (Interoperable Java Card) file [back] to a CAP file.
|
"""Convert an ICJ (Interoperable Java Card) file [back] to a CAP file.
|
||||||
@@ -19,3 +40,103 @@ def ijc_to_cap(in_file: io.IOBase, out_zip: zipfile.ZipFile, p : str = "foo"):
|
|||||||
out_zip.writestr(p+"/javacard/"+TAGS[tag-1]+".cap", b[0:3+size])
|
out_zip.writestr(p+"/javacard/"+TAGS[tag-1]+".cap", b[0:3+size])
|
||||||
b = b[3+size:]
|
b = b[3+size:]
|
||||||
|
|
||||||
|
class CapFile():
|
||||||
|
|
||||||
|
# Java Card Platform Virtual Machine Specification, v3.2, section 6.4
|
||||||
|
__header_component_compact = Struct('tag'/Int8ub,
|
||||||
|
'size'/Int16ub,
|
||||||
|
'magic'/Int32ub,
|
||||||
|
'minor_version'/Int8ub,
|
||||||
|
'major_version'/Int8ub,
|
||||||
|
'flags'/Int8ub,
|
||||||
|
'package'/Struct('minor_version'/Int8ub,
|
||||||
|
'major_version'/Int8ub,
|
||||||
|
'AID'/LV),
|
||||||
|
'package_name'/COptional(LV)) #since CAP format 2.2
|
||||||
|
|
||||||
|
# Java Card Platform Virtual Machine Specification, v3.2, section 6.6
|
||||||
|
__applet_component_compact = Struct('tag'/Int8ub,
|
||||||
|
'size'/Int16ub,
|
||||||
|
'count'/Int8ub,
|
||||||
|
'applets'/Array(this.count, Struct('AID'/LV,
|
||||||
|
'install_method_offset'/Int16ub)),
|
||||||
|
)
|
||||||
|
|
||||||
|
def __init__(self, filename:str):
|
||||||
|
|
||||||
|
# In this dictionary we will keep all nested .cap file components by their file names (without .cap suffix)
|
||||||
|
# See also: Java Card Platform Virtual Machine Specification, v3.2, section 6.2.1
|
||||||
|
self.__component = {}
|
||||||
|
|
||||||
|
# Extract the nested .cap components from the .cap file
|
||||||
|
# See also: Java Card Platform Virtual Machine Specification, v3.2, section 6.2.1
|
||||||
|
cap = zipfile.ZipFile(filename)
|
||||||
|
cap_namelist = cap.namelist()
|
||||||
|
for i, filename in enumerate(cap_namelist):
|
||||||
|
if filename.lower().endswith('.capx') and not filename.lower().endswith('.capx'):
|
||||||
|
#TODO: At the moment we only support the compact .cap format, add support for the extended .cap format.
|
||||||
|
raise ValueError("incompatible .cap file, extended .cap format not supported!")
|
||||||
|
|
||||||
|
if filename.lower().endswith('.cap'):
|
||||||
|
key = filename.split('/')[-1].removesuffix('.cap')
|
||||||
|
self.__component[key] = cap.read(filename)
|
||||||
|
|
||||||
|
# Make sure that all mandatory components are present
|
||||||
|
# See also: Java Card Platform Virtual Machine Specification, v3.2, section 6.2
|
||||||
|
required_components = {'Header' : 'COMPONENT_Header',
|
||||||
|
'Directory' : 'COMPONENT_Directory',
|
||||||
|
'Import' : 'COMPONENT_Import',
|
||||||
|
'ConstantPool' : 'COMPONENT_ConstantPool',
|
||||||
|
'Class' : 'COMPONENT_Class',
|
||||||
|
'Method' : 'COMPONENT_Method',
|
||||||
|
'StaticField' : 'COMPONENT_StaticField',
|
||||||
|
'RefLocation' : 'COMPONENT_ReferenceLocation',
|
||||||
|
'Descriptor' : 'COMPONENT_Descriptor'}
|
||||||
|
for component in required_components:
|
||||||
|
if component not in self.__component.keys():
|
||||||
|
raise ValueError("invalid cap file, %s missing!" % required_components[component])
|
||||||
|
|
||||||
|
def get_loadfile(self) -> bytes:
|
||||||
|
"""Get the executeable loadfile as hexstring"""
|
||||||
|
# Concatenate all cap file components in the specified order
|
||||||
|
# see also: Java Card Platform Virtual Machine Specification, v3.2, section 6.3
|
||||||
|
loadfile = self.__component['Header']
|
||||||
|
loadfile += self.__component['Directory']
|
||||||
|
loadfile += self.__component['Import']
|
||||||
|
if 'Applet' in self.__component.keys():
|
||||||
|
loadfile += self.__component['Applet']
|
||||||
|
loadfile += self.__component['Class']
|
||||||
|
loadfile += self.__component['Method']
|
||||||
|
loadfile += self.__component['StaticField']
|
||||||
|
if 'Export' in self.__component.keys():
|
||||||
|
loadfile += self.__component['Export']
|
||||||
|
loadfile += self.__component['ConstantPool']
|
||||||
|
loadfile += self.__component['RefLocation']
|
||||||
|
if 'Descriptor' in self.__component.keys():
|
||||||
|
loadfile += self.__component['Descriptor']
|
||||||
|
return loadfile
|
||||||
|
|
||||||
|
def get_loadfile_aid(self) -> Hexstr:
|
||||||
|
"""Get the loadfile AID as hexstring"""
|
||||||
|
header = self.__header_component_compact.parse(self.__component['Header'])
|
||||||
|
magic = header['magic'] or 0
|
||||||
|
if magic != 0xDECAFFED:
|
||||||
|
raise ValueError("invalid cap file, COMPONENT_Header lacks magic number (0x%08X!=0xDECAFFED)!" % magic)
|
||||||
|
#TODO: check cap version and make sure we are compatible with it
|
||||||
|
return header['package']['AID']
|
||||||
|
|
||||||
|
def get_applet_aid(self, index:int = 0) -> Hexstr:
|
||||||
|
"""Get the applet AID as hexstring"""
|
||||||
|
#To get the module AID, we must look into COMPONENT_Applet. Unfortunately, even though this component should
|
||||||
|
#be present in any .cap file, it is defined as an optional component.
|
||||||
|
if 'Applet' not in self.__component.keys():
|
||||||
|
raise ValueError("can't get the AID, this cap file lacks the optional COMPONENT_Applet component!")
|
||||||
|
|
||||||
|
applet = self.__applet_component_compact.parse(self.__component['Applet'])
|
||||||
|
|
||||||
|
if index > applet['count']:
|
||||||
|
raise ValueError("can't get the AID for applet with index=%u, this .cap file only has %u applets!" %
|
||||||
|
(index, applet['count']))
|
||||||
|
|
||||||
|
return applet['applets'][index]['AID']
|
||||||
|
|
||||||
|
|||||||
BIN
tests/unittests/test_javacard.cap
Normal file
BIN
tests/unittests/test_javacard.cap
Normal file
Binary file not shown.
18
tests/unittests/test_javacard.py
Executable file
18
tests/unittests/test_javacard.py
Executable file
@@ -0,0 +1,18 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import os
|
||||||
|
import unittest
|
||||||
|
from pySim.javacard import *
|
||||||
|
|
||||||
|
class TestJavacard(unittest.TestCase):
|
||||||
|
|
||||||
|
def test_CapFile(self):
|
||||||
|
loadfile="01000fdecaffed010204000105d07002ca4402001f000f001f000c002800420018006d00320017000000a900040002002203010004002803020107a0000000620101060210a0000000090003ffffffff8910710002000107a000000062000103000c0108d07002ca44900101002006001843800301ff0007020000002f00398002008101010881000007006d000911188c00048d00012c18197b0002037b00029210240303038b000388007a02318f00053d8c00062e1b8b00077a0120188b000860037a7a02228d00092d1d10076b101a8b000a321fae006b06188c000b7a06118d000c2c1903077b000d037b000d928b000e198b000f3b7a08003200040002000203001857656c636f6d6520746f20546f6f7243616d70203230313203000a48656c6c6f2c2053544b0000000005004200100200000006810900050000020381090b0680030001000000060000010380030103800303068108000381080c0600005306810a000500000003810a1303810a1609001700021e2d0011050306040908040507090a0a06070404040b00a901000100000300030005800281018100ff080000000028ff08000002002800020000008003ff820001002f001d0000000000090020003f000d000000000701002f0042000800000000080100390046001800000000ff020053002f0018000000000010002200240028002a002fffff002f002f003100330022002f00370028003b002201300568109001b008b44323430110012005681080056810a00633b44104b431066800a10231"
|
||||||
|
cap = CapFile(os.path.dirname(os.path.abspath(__file__)) + "/test_javacard.cap")
|
||||||
|
self.assertTrue(b2h(cap.get_loadfile()) == loadfile)
|
||||||
|
self.assertTrue(cap.get_loadfile_aid() == "d07002ca44")
|
||||||
|
self.assertTrue(cap.get_applet_aid() == "d07002ca44900101")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
Reference in New Issue
Block a user