dexter has uploaded this change for review. ( https://gerrit.osmocom.org/c/pysim/+/39198?usp=email )
Change subject: javacard: add parser for JAVA-card CAP file format ......................................................................
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 --- M pySim/javacard.py A tests/unittests/test_javacard.cap A tests/unittests/test_javacard.py 3 files changed, 173 insertions(+), 1 deletion(-)
git pull ssh://gerrit.osmocom.org:29418/pysim refs/changes/98/39198/1
diff --git a/pySim/javacard.py b/pySim/javacard.py index 07b0f39..c304999 100644 --- a/pySim/javacard.py +++ b/pySim/javacard.py @@ -1,9 +1,30 @@ # 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 struct import sys import io +from osmocom.utils import b2h +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"): """Convert an ICJ (Interoperable Java Card) file [back] to a CAP file. @@ -19,3 +40,136 @@ out_zip.writestr(p+"/javacard/"+TAGS[tag-1]+".cap", b[0:3+size]) b = b[3+size:]
+class CapFile(): + + #TODO: At the moment we only support the compact .cap format, add support for the extended .cap format. + + __component_header = None + __component_directory = None + __component_applet = None #optional + __component_import = None + __component_constantPool = None + __component_class = None + __component_method = None + __component_staticField = None + __component_referenceLocation = None + __component_export = None #optional + __component_descriptor = None + __component_debug = None #optional, since CAP format 2.2 + + # 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): + # 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('header.cap'): + self.__component_header = cap.read(filename) + elif filename.lower().endswith('directory.cap'): + self.__component_directory = cap.read(filename) + elif filename.lower().endswith('applet.cap'): + self.__component_applet = cap.read(filename) + elif filename.lower().endswith('import.cap'): + self.__component_import = cap.read(filename) + elif filename.lower().endswith('constantpool.cap'): + self.__component_constantPool = cap.read(filename) + elif filename.lower().endswith('class.cap'): + self.__component_class = cap.read(filename) + elif filename.lower().endswith('method.cap'): + self.__component_method = cap.read(filename) + elif filename.lower().endswith('staticfield.cap'): + self.__component_staticField = cap.read(filename) + elif filename.lower().endswith('reflocation.cap'): + self.__component_referenceLocation = cap.read(filename) + elif filename.lower().endswith('export.cap'): + self.__component_export = cap.read(filename) + elif filename.lower().endswith('descriptor.cap'): + self.__component_descriptor = cap.read(filename) + elif filename.lower().endswith('debug.cap'): + self.__component_debug = cap.read(filename) + + # Make sure that all mandatory components are present + # See also: Java Card Platform Virtual Machine Specification, v3.2, section 6.2 + if self.__component_header is None: + raise ValueError("invalid cap file, COMPONENT_Header missing!") + if self.__component_directory is None: + raise ValueError("invalid cap file, COMPONENT_Directory missing!") + if self.__component_import is None: + raise ValueError("invalid cap file, COMPONENT_Import missing!") + if self.__component_constantPool is None: + raise ValueError("invalid cap file, COMPONENT_ConstantPool missing!") + if self.__component_class is None: + raise ValueError("invalid cap file, COMPONENT_Class missing!") + if self.__component_method is None: + raise ValueError("invalid cap file, COMPONENT_Method missing!") + if self.__component_staticField is None: + raise ValueError("invalid cap file, COMPONENT_StaticField missing!") + if self.__component_referenceLocation is None: + raise ValueError("invalid cap file, COMPONENT_ReferenceLocation missing!") + if self.__component_descriptor is None: + raise ValueError("invalid cap file, COMPONENT_Descriptor missing!") + + def get_loadfile(self): + """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 self.__component_applet: + loadfile += self.__component_applet + loadfile += self.__component_class + loadfile += self.__component_method + loadfile += self.__component_staticField + if self.__component_export: + loadfile += self.__component_export + loadfile += self.__component_constantPool + loadfile += self.__component_referenceLocation + if self.__component_descriptor: + loadfile += self.__component_descriptor + return b2h(loadfile) + + def get_loadfile_aid(self): + """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): + """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 self.__component_applet is None: + 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'] + diff --git a/tests/unittests/test_javacard.cap b/tests/unittests/test_javacard.cap new file mode 100644 index 0000000..f96ea24 --- /dev/null +++ b/tests/unittests/test_javacard.cap Binary files differ diff --git a/tests/unittests/test_javacard.py b/tests/unittests/test_javacard.py new file mode 100755 index 0000000..30d8bdd --- /dev/null +++ b/tests/unittests/test_javacard.py @@ -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(cap.get_loadfile() == loadfile) + self.assertTrue(cap.get_loadfile_aid() == "d07002ca44") + self.assertTrue(cap.get_applet_aid() == "d07002ca44900101") + + +if __name__ == "__main__": + unittest.main()