diff --git a/pySim-shell.py b/pySim-shell.py index 41febd63..a8471ff5 100755 --- a/pySim-shell.py +++ b/pySim-shell.py @@ -29,6 +29,7 @@ import argparse import os import sys from optparse import OptionParser +from pathlib import Path from pySim.ts_51_011 import EF, DF, EF_SST_map, EF_AD_mode_map from pySim.ts_31_102 import EF_UST_map, EF_USIM_ADF_map @@ -47,6 +48,9 @@ from pySim.ts_102_221 import CardProfileUICC from pySim.ts_31_102 import ADF_USIM from pySim.ts_31_103 import ADF_ISIM +from pySim.card_data import CardDataCsv, card_data_register, card_data_get_field + + class PysimApp(cmd2.Cmd): CUSTOM_CATEGORY = 'pySim Commands' def __init__(self, card, rs, script = None): @@ -56,6 +60,8 @@ class PysimApp(cmd2.Cmd): self.intro = style('Welcome to pySim-shell!', fg=fg.red) self.default_category = 'pySim-shell built-in commands' self.card = card + iccid, sw = self.card.read_iccid() + self.iccid = iccid self.rs = rs self.py_locals = { 'card': self.card, 'rs' : self.rs } self.numeric_path = False @@ -78,8 +84,20 @@ class PysimApp(cmd2.Cmd): @cmd2.with_category(CUSTOM_CATEGORY) def do_verify_adm(self, arg): """VERIFY the ADM1 PIN""" - pin_adm = sanitize_pin_adm(arg) - self.card.verify_adm(h2b(pin_adm)) + if arg: + # use specified ADM-PIN + pin_adm = sanitize_pin_adm(arg) + else: + # try to find an ADM-PIN if none is specified + result = card_data_get_field('ADM1', key='ICCID', value=self.iccid) + pin_adm = sanitize_pin_adm(result) + if pin_adm: + self.poutput("found adm-pin '%s' for ICCID '%s'" % (result, self.iccid)) + + if pin_adm: + self.card.verify_adm(h2b(pin_adm)) + else: + self.poutput("error: cannot authenticate, no adm-pin!") @cmd2.with_category(CUSTOM_CATEGORY) def do_desc(self, opts): @@ -289,6 +307,11 @@ def parse_options(): default=None, ) + parser.add_option("--csv", dest="csv", metavar="FILE", + help="Read card data from CSV file", + default=None, + ) + parser.add_option("-a", "--pin-adm", dest="pin_adm", help="ADM PIN used for provisioning (overwrites default)", ) @@ -340,6 +363,14 @@ if __name__ == '__main__': app = PysimApp(card, rs, opts.script) rs.select('MF', app) + # Register csv-file as card data provider, either from specified CSV + # or from CSV file in home directory + csv_default = str(Path.home()) + "/.osmocom/pysim/card_data.csv" + if opts.csv: + card_data_register(CardDataCsv(opts.csv)) + if os.path.isfile(csv_default): + card_data_register(CardDataCsv(csv_default)) + # If the user supplies an ADM PIN at via commandline args authenticate # immediatley so that the user does not have to use the shell commands pin_adm = sanitize_pin_adm(opts.pin_adm, opts.pin_adm_hex) diff --git a/pySim/card_data.py b/pySim/card_data.py new file mode 100644 index 00000000..495c1f3b --- /dev/null +++ b/pySim/card_data.py @@ -0,0 +1,113 @@ +# coding=utf-8 +"""Abstraction of card data that can be queried from external source + +(C) 2021 by Sysmocom s.f.m.c. GmbH +All Rights Reserved + +Author: Philipp Maier + +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 . +""" + +import csv + +card_data_provider = [] + +class CardData(object): + + VALID_FIELD_NAMES = ['ICCID', 'ADM1', 'IMSI'] + + # check input parameters, but do nothing concrete yet + def get_data(self, fields=[], key='ICCID', value=""): + """abstract implementation of get_data that only verifies the function parameters""" + + for f in fields: + if (f not in self.VALID_FIELD_NAMES): + raise ValueError("Requested field name '%s' is not a valid field name, valid field names are: %s" % + (f, str(self.VALID_FIELD_NAMES))) + + if (key not in self.VALID_FIELD_NAMES): + raise ValueError("Key field name '%s' is not a valid field name, valid field names are: %s" % + (key, str(self.VALID_FIELD_NAMES))) + + return {} + + def get_field(self, field, key='ICCID', value=""): + """get a single field from CSV file using a specified key/value pair""" + fields = [field] + result = self.get(fields, key, value) + return result.get(field) + + +class CardDataCsv(CardData): + """card data class that allows the user to query against a specified CSV file""" + csv_file = None + filename = None + + def __init__(self, filename): + self.csv_file = open(filename, 'r') + if not self.csv_file: + raise RuntimeError("Could not open CSV-File '%s'" % filename) + self.filename = filename + + def get(self, fields, key, value): + """get fields from CSV file using a specified key/value pair""" + super().get_data(fields, key, value) + + self.csv_file.seek(0) + cr = csv.DictReader(self.csv_file) + if not cr: + raise RuntimeError("Could not open DictReader for CSV-File '%s'" % self.filename) + cr.fieldnames = [ field.upper() for field in cr.fieldnames ] + + rc = {} + for row in cr: + if row[key] == value: + for f in fields: + if f in row: + rc.update({f : row[f]}) + else: + raise RuntimeError("CSV-File '%s' lacks column '%s'" % + (self.filename, f)) + return rc + + +def card_data_register(provider, provider_list=card_data_provider): + """Register a new card data provider""" + if not isinstance(provider, CardData): + raise ValueError("provider is not a card data provier") + provider_list.append(provider) + + +def card_data_get(fields, key, value, provider_list=card_data_provider): + """Query all registered card data providers""" + for p in provider_list: + if not isinstance(p, CardData): + raise ValueError("provider list contains provider, which is not a card data provier") + result = p.get(fields, key, value) + if result: + return result + return {} + + +def card_data_get_field(field, key, value, provider_list=card_data_provider): + """Query all registered card data providers for a single field""" + for p in provider_list: + if not isinstance(p, CardData): + raise ValueError("provider list contains provider, which is not a card data provier") + result = p.get_field(field, key, value) + if result: + return result + return None +