laforge has uploaded this change for review. ( 
https://gerrit.osmocom.org/c/pysim/+/37856?usp=email )


Change subject: WIP: contrib/fsdump2saip
......................................................................

WIP: contrib/fsdump2saip

Change-Id: I8fc23b4177f229170a6ee99eca8863518196fa51
---
A contrib/fsdump2saip.py
1 file changed, 202 insertions(+), 0 deletions(-)



  git pull ssh://gerrit.osmocom.org:29418/pysim refs/changes/56/37856/1

diff --git a/contrib/fsdump2saip.py b/contrib/fsdump2saip.py
new file mode 100755
index 0000000..7b14313
--- /dev/null
+++ b/contrib/fsdump2saip.py
@@ -0,0 +1,202 @@
+#!/usr/bin/env python3
+
+# (C) 2024 by Harald Welte <[email protected]>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 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 Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+
+# This is a script to generate a [partial] eSIM profile from the 'fsdump' of 
another USIM/ISIM.  This is
+# useful to generate an "as close as possible" eSIM from a physical USIM, as 
far as that is possible
+# programmatically and in a portable way.
+#
+# Of course, this really only affects the file sytem aspects of the card.  
It's not possible
+# to read the K/OPc or other authentication related parameters off a random 
USIM, and hence
+# we cannot replicate that.  Similarly, it's not possible to export the java 
applets from a USIM,
+# and hence we cannot replicate those.
+
+import argparse
+
+from pySim.esim.saip import *
+from pySim.ts_102_221 import *
+
+class FsdumpToSaip:
+    def __init__(self, pes: ProfileElementSequence):
+        self.pes = pes
+
+    @staticmethod
+    def fcp_raw2saip_fcp(fname:str, fcp_raw: bytes) -> Dict:
+        """Convert a raw TLV-encoded FCP to a SAIP dict-format (as needed by 
asn1encode)."""
+        ftype = fname.split('.')[0]
+        # use the raw FCP as basis so we don't get stuck with potentially old 
decoder bugs
+        # ore future format incompatibilities
+        fcp = FcpTemplate()
+        fcp.from_tlv(fcp_raw)
+        r = {}
+
+        r['fileDescriptor'] = fcp.child_by_type(FileDescriptor).to_bytes()
+
+        file_id = fcp.child_by_type(FileIdentifier)
+        if file_id:
+            r['fileID'] = file_id.to_bytes()
+        else:
+            if ftype in ['ADF']:
+                print('%s is an ADF but has no [mandatory] file_id!' % fname)
+                #raise ValueError('%s is an ADF but has no [mandatory] 
file_id!' % fname)
+                r['fileID'] = b'\x7f\xff' # FIXME: auto-increment
+
+        df_name = fcp.child_by_type(DfName)
+        if ftype in ['ADF']:
+            if not df_name:
+                raise ValueError('%s is an ADF but has no [mandatory] 
df_name!' % fname)
+            r['dfName'] = df_name.to_bytes()
+
+        lcsi_byte = fcp.child_by_type(LifeCycleStatusInteger).to_bytes()
+        if lcsi_byte != b'\x05':
+            r['lcsi'] = lcsi_byte
+
+        sa_ref = fcp.child_by_type(SecurityAttribReferenced)
+        if sa_ref:
+            r['securityAttributesReferenced'] = sa_ref.to_bytes()
+
+        file_size = fcp.child_by_type(FileSize)
+        if ftype in ['EF']:
+            if file_size:
+                r['efFileSize'] = file_size.to_bytes()
+
+        psdo = fcp.child_by_type(PinStatusTemplate_DO)
+        if ftype in ['MF', 'ADF', 'DF']:
+            if not psdo:
+                raise ValueError('%s is an %s but has no [mandatory] 
PinStatusTemplateDO' % fname)
+            else:
+                r['pinStatusTemplateDO'] = psdo.to_bytes()
+
+        sfid = fcp.child_by_type(ShortFileIdentifier)
+        if sfid and sfid.decoded:
+            if ftype not in ['EF']:
+                raise ValueError('%s is not an EF but has [forbidden] 
shortEFID' % fname)
+            r['shortEFID'] = sfid.to_bytes()
+
+        pinfo = fcp.child_by_type(ProprietaryInformation)
+        if pinfo and ftype in ['EF']:
+            spinfo = pinfo.child_by_type(SpecialFileInfo)
+            fill_p = pinfo.child_by_type(FillingPattern)
+            repeat_p = pinfo.child_by_type(RepeatPattern)
+
+            if spinfo or fill_p or repeat_p:
+                r['proprietaryEFInfo'] = {}
+            if spinfo:
+                r['proprietaryEFInfo']['specialFileInformation'] = 
spinfo.to_bytes()
+            if fill_p:
+                r['proprietaryEFInfo']['fillPattern'] = fill_p.to_bytes()
+            if repeat_p:
+                r['proprietaryEFInfo']['repeatPattern'] = repeat_p.to_bytes()
+
+        # TODO: linkPath
+        return r
+
+    @staticmethod
+    def fcp_fsdump2saip(fsdump_ef: Dict):
+        """Convert a file from its "fsdump" representation to the SAIP 
representation of a File type
+        in the decoded format as used by the asn1tools-generated codec."""
+        # first convert the FCP
+        path = fsdump_ef['path']
+        fdesc = FsdumpToSaip.fcp_raw2saip_fcp(path[-1], 
h2b(fsdump_ef['fcp_raw']))
+        r = [
+                ('fileDescriptor', fdesc),
+        ]
+        # then convert the body.  We're doing a straight-forward conversion 
without any optimization
+        # like not encoding all-ff files.  This should be done by a subsequent 
optimizer
+        if 'body' in fsdump_ef and fsdump_ef['body']:
+            if isinstance(fsdump_ef['body'], list):
+                for b_seg in fsdump_ef['body']:
+                    r.append(('fillFileContent', h2b(b_seg)))
+            else:
+                r.append(('fillFileContent', h2b(fsdump_ef['body'])))
+        print(fsdump_ef['path'])
+        return r
+
+    def add_file_from_fsdump(self, fsdump_ef: Dict):
+        fid = int(fsdump_ef['fcp']['file_identifier'])
+        # determine NAA
+        if fsdump_ef['path'][0:1] == ['MF', 'ADF.USIM']:
+            naa = NaaUsim
+        elif fsdump_ef['path'][0:1] == ['MF', 'ADF.ISIM']:
+            naa = NaaIsim
+        else:
+            print("Unable to determine NAA for %s" % fsdump_ef['path'])
+            return
+        pes.pes_by_naa[naa.name]
+        for pe in pes:
+            print("PE %s" % pe)
+            if not isinstance(pe, FsProfileElement):
+                print("Skipping PE %s" % pe)
+                continue
+            if not pe.template:
+                print("No template for PE %s" % pe )
+                continue
+            if not fid in pe.template.files_by_fid:
+                print("File %04x not available in template; must create it 
using GenericFileMgmt" % fid)
+
+parser = argparse.ArgumentParser()
+parser.add_argument('fsdump', help='')
+
+def has_unsupported_path_prefix(path: List[str]) -> bool:
+    # skip some paths from export as they don't exist in an eSIM profile
+    UNSUPPORTED_PATHS = [
+        ['MF', 'DF.GSM'],
+    ]
+    for p in UNSUPPORTED_PATHS:
+        prefix = path[:len(p)]
+        if prefix == p:
+            return True
+    # any ADF not USIM or ISIM are unsupported
+    SUPPORTED_ADFS = [ 'ADF.USIM', 'ADF.ISIM' ]
+    if len(path) == 2 and path[0] == 'MF' and path[1].startswith('ADF.') and 
path[1] not in SUPPORTED_ADFS:
+        return True
+    return False
+
+import traceback
+
+if __name__ == '__main__':
+    opts = parser.parse_args()
+
+    with open(opts.fsdump, 'r') as f:
+        fsdump = json.loads(f.read())
+
+    pes = ProfileElementSequence()
+
+    # FIXME: fsdump has strting-name path, but we need FID-list path for 
ProfileElementSequence
+    for path, fsdump_ef in fsdump['files'].items():
+        print("=" * 80)
+        #print(fsdump_ef)
+        if not 'fcp_raw' in fsdump_ef:
+            continue
+        if has_unsupported_path_prefix(fsdump_ef['path']):
+            print("Skipping eSIM-unsupported path %s" % 
('/'.join(fsdump_ef['path'])))
+            continue
+        saip_dec = FsdumpToSaip.fcp_fsdump2saip(fsdump_ef)
+        #print(saip_dec)
+        try:
+            f = pes.add_file_at_path(Path(path), saip_dec)
+            print(repr(f))
+        except Exception as e:
+            print("EXCEPTION: %s" % traceback.format_exc())
+            #print("EXCEPTION: %s" % e)
+            continue
+
+    print("=== Tree === ")
+    pes.mf.print_tree()
+
+    # FIXME: export the actual PE Sequence
+

--
To view, visit https://gerrit.osmocom.org/c/pysim/+/37856?usp=email
To unsubscribe, or for help writing mail filters, visit 
https://gerrit.osmocom.org/settings?usp=email

Gerrit-MessageType: newchange
Gerrit-Project: pysim
Gerrit-Branch: master
Gerrit-Change-Id: I8fc23b4177f229170a6ee99eca8863518196fa51
Gerrit-Change-Number: 37856
Gerrit-PatchSet: 1
Gerrit-Owner: laforge <[email protected]>

Reply via email to