<p>dexter has uploaded this change for <strong>review</strong>.</p><p><a href="https://gerrit.osmocom.org/c/pysim/+/23538">View Change</a></p><pre style="font-family: monospace,monospace; white-space: pre-wrap;">pySim-shell: automatic ADM pin from CSV-File<br><br>It can be hard to manage ADM pins when working with different cards at<br>the same time. To make this easier, add an automatic way to determine<br>the ADM pin for each card from a CSV file.<br><br>- add a CardData clas model that can be extended to to get the data from<br> various different sources. For now use CSV-Files. Also add a way how<br> multiple CardData classes can be registered so that one global get<br> function can query all registered CardData classes at once.<br><br>- automatically check for CSV-File in home directory and use it as<br> default CardData source unless the user specifies a CSV file via<br> commandline argument.<br><br>- extend the verify_adm command so that it automatically queries the<br> ADM pin if no argument is given. Also do not try to authenticate if<br> no ADM pin could be determined.<br><br>Change-Id: I51835ccb16bcbce35e7f3765e8927a4451509e77<br>Related: OS#4963<br>---<br>M pySim-shell.py<br>A pySim/card_data.py<br>2 files changed, 146 insertions(+), 2 deletions(-)<br><br></pre><pre style="font-family: monospace,monospace; white-space: pre-wrap;">git pull ssh://gerrit.osmocom.org:29418/pysim refs/changes/38/23538/1</pre><pre style="font-family: monospace,monospace; white-space: pre-wrap;"><span>diff --git a/pySim-shell.py b/pySim-shell.py</span><br><span>index 41febd6..3f26ac5 100755</span><br><span>--- a/pySim-shell.py</span><br><span>+++ b/pySim-shell.py</span><br><span>@@ -29,6 +29,7 @@</span><br><span> import os</span><br><span> import sys</span><br><span> from optparse import OptionParser</span><br><span style="color: hsl(120, 100%, 40%);">+from pathlib import Path</span><br><span> </span><br><span> from pySim.ts_51_011 import EF, DF, EF_SST_map, EF_AD_mode_map</span><br><span> from pySim.ts_31_102 import EF_UST_map, EF_USIM_ADF_map</span><br><span>@@ -47,6 +48,9 @@</span><br><span> from pySim.ts_31_102 import ADF_USIM</span><br><span> from pySim.ts_31_103 import ADF_ISIM</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+from pySim.card_data import CardDataCsv, card_data_register, card_data_get_field</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> class PysimApp(cmd2.Cmd):</span><br><span> CUSTOM_CATEGORY = 'pySim Commands'</span><br><span> def __init__(self, card, rs, script = None):</span><br><span>@@ -56,6 +60,8 @@</span><br><span> self.intro = style('Welcome to pySim-shell!', fg=fg.red)</span><br><span> self.default_category = 'pySim-shell built-in commands'</span><br><span> self.card = card</span><br><span style="color: hsl(120, 100%, 40%);">+ iccid, sw = self.card.read_iccid()</span><br><span style="color: hsl(120, 100%, 40%);">+ self.iccid = iccid</span><br><span> self.rs = rs</span><br><span> self.py_locals = { 'card': self.card, 'rs' : self.rs }</span><br><span> self.numeric_path = False</span><br><span>@@ -78,8 +84,20 @@</span><br><span> @cmd2.with_category(CUSTOM_CATEGORY)</span><br><span> def do_verify_adm(self, arg):</span><br><span> """VERIFY the ADM1 PIN"""</span><br><span style="color: hsl(0, 100%, 40%);">- pin_adm = sanitize_pin_adm(arg)</span><br><span style="color: hsl(0, 100%, 40%);">- self.card.verify_adm(h2b(pin_adm))</span><br><span style="color: hsl(120, 100%, 40%);">+ if arg:</span><br><span style="color: hsl(120, 100%, 40%);">+ # use specified ADM-PIN</span><br><span style="color: hsl(120, 100%, 40%);">+ pin_adm = sanitize_pin_adm(arg)</span><br><span style="color: hsl(120, 100%, 40%);">+ else:</span><br><span style="color: hsl(120, 100%, 40%);">+ # try to find an ADM-PIN if none is specified</span><br><span style="color: hsl(120, 100%, 40%);">+ result = card_data_get_field('ADM1', key='ICCID', value=self.iccid)</span><br><span style="color: hsl(120, 100%, 40%);">+ pin_adm = sanitize_pin_adm(result)</span><br><span style="color: hsl(120, 100%, 40%);">+ if pin_adm:</span><br><span style="color: hsl(120, 100%, 40%);">+ self.poutput("found adm-pin '%s' for ICCID '%s'" % (result, self.iccid))</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ if pin_adm:</span><br><span style="color: hsl(120, 100%, 40%);">+ self.card.verify_adm(h2b(pin_adm))</span><br><span style="color: hsl(120, 100%, 40%);">+ else:</span><br><span style="color: hsl(120, 100%, 40%);">+ self.poutput("error: cannot authenticate, no adm-pin!")</span><br><span> </span><br><span> @cmd2.with_category(CUSTOM_CATEGORY)</span><br><span> def do_desc(self, opts):</span><br><span>@@ -289,6 +307,11 @@</span><br><span> default=None,</span><br><span> )</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+ parser.add_option("--csv", dest="csv", metavar="FILE",</span><br><span style="color: hsl(120, 100%, 40%);">+ help="Read card data from CSV file",</span><br><span style="color: hsl(120, 100%, 40%);">+ default=None,</span><br><span style="color: hsl(120, 100%, 40%);">+ )</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> parser.add_option("-a", "--pin-adm", dest="pin_adm",</span><br><span> help="ADM PIN used for provisioning (overwrites default)",</span><br><span> )</span><br><span>@@ -340,6 +363,14 @@</span><br><span> app = PysimApp(card, rs, opts.script)</span><br><span> rs.select('MF', app)</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+ # Register csv-file as card data provider, either from specified CSV</span><br><span style="color: hsl(120, 100%, 40%);">+ # or from CSV file in home directory</span><br><span style="color: hsl(120, 100%, 40%);">+ csv_default = str(Path.home()) + "/.pysim_shell_card_data.csv"</span><br><span style="color: hsl(120, 100%, 40%);">+ if opts.csv:</span><br><span style="color: hsl(120, 100%, 40%);">+ card_data_register(CardDataCsv(opts.csv))</span><br><span style="color: hsl(120, 100%, 40%);">+ elif os.path.isfile(csv_default):</span><br><span style="color: hsl(120, 100%, 40%);">+ card_data_register(CardDataCsv(csv_default))</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> # If the user supplies an ADM PIN at via commandline args authenticate</span><br><span> # immediatley so that the user does not have to use the shell commands</span><br><span> pin_adm = sanitize_pin_adm(opts.pin_adm, opts.pin_adm_hex)</span><br><span>diff --git a/pySim/card_data.py b/pySim/card_data.py</span><br><span>new file mode 100644</span><br><span>index 0000000..8feb84b</span><br><span>--- /dev/null</span><br><span>+++ b/pySim/card_data.py</span><br><span>@@ -0,0 +1,113 @@</span><br><span style="color: hsl(120, 100%, 40%);">+# coding=utf-8</span><br><span style="color: hsl(120, 100%, 40%);">+"""Abstraction of card data that can be queried from external source</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+(C) 2021 by Sysmocom s.f.m.c. GmbH</span><br><span style="color: hsl(120, 100%, 40%);">+All Rights Reserved</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+Author: Philipp Maier</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+This program is free software: you can redistribute it and/or modify</span><br><span style="color: hsl(120, 100%, 40%);">+it under the terms of the GNU General Public License as published by</span><br><span style="color: hsl(120, 100%, 40%);">+the Free Software Foundation, either version 2 of the License, or</span><br><span style="color: hsl(120, 100%, 40%);">+(at your option) any later version.</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+This program is distributed in the hope that it will be useful,</span><br><span style="color: hsl(120, 100%, 40%);">+but WITHOUT ANY WARRANTY; without even the implied warranty of</span><br><span style="color: hsl(120, 100%, 40%);">+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the</span><br><span style="color: hsl(120, 100%, 40%);">+GNU General Public License for more details.</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+You should have received a copy of the GNU General Public License</span><br><span style="color: hsl(120, 100%, 40%);">+along with this program. If not, see <http://www.gnu.org/licenses/>.</span><br><span style="color: hsl(120, 100%, 40%);">+"""</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+import csv</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+card_data_provider = []</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+class CardData(object):</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ VALID_FIELD_NAMES = ['ICCID', 'ADM1', 'IMSI']</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ # check input parameters, but do nothing concrete yet</span><br><span style="color: hsl(120, 100%, 40%);">+ def get_data(self, fields=[], key='ICCID', value=""):</span><br><span style="color: hsl(120, 100%, 40%);">+ """abstract implementation of gst_data that only verifies the function parameters"""</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ for f in fields:</span><br><span style="color: hsl(120, 100%, 40%);">+ if (f not in self.VALID_FIELD_NAMES):</span><br><span style="color: hsl(120, 100%, 40%);">+ raise ValueError("Requested field name '%s' is not a valid field name, valid field names are: %s" %</span><br><span style="color: hsl(120, 100%, 40%);">+ (f, str(self.VALID_FIELD_NAMES)))</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ if (key not in self.VALID_FIELD_NAMES):</span><br><span style="color: hsl(120, 100%, 40%);">+ raise ValueError("Key field name '%s' is not a valid field name, valid field names are: %s" %</span><br><span style="color: hsl(120, 100%, 40%);">+ (key, str(self.VALID_FIELD_NAMES)))</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ return {}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ def get_field(self, field, key='ICCID', value=""):</span><br><span style="color: hsl(120, 100%, 40%);">+ """get a single field from CSV file using a specified key/value pair"""</span><br><span style="color: hsl(120, 100%, 40%);">+ fields = [field]</span><br><span style="color: hsl(120, 100%, 40%);">+ result = self.get(fields, key, value)</span><br><span style="color: hsl(120, 100%, 40%);">+ return result.get(field)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+class CardDataCsv(CardData):</span><br><span style="color: hsl(120, 100%, 40%);">+ """card data class that allows the user to query against a specified CSV file"""</span><br><span style="color: hsl(120, 100%, 40%);">+ csv_file = None</span><br><span style="color: hsl(120, 100%, 40%);">+ filename = None</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ def __init__(self, filename):</span><br><span style="color: hsl(120, 100%, 40%);">+ self.csv_file = open(filename, 'r')</span><br><span style="color: hsl(120, 100%, 40%);">+ if not self.csv_file:</span><br><span style="color: hsl(120, 100%, 40%);">+ raise RuntimeError("Could not open CSV-File '%s'" % filename)</span><br><span style="color: hsl(120, 100%, 40%);">+ self.filename = filename</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ def get(self, fields, key, value):</span><br><span style="color: hsl(120, 100%, 40%);">+ """get fields from CSV file using a specified key/value pair"""</span><br><span style="color: hsl(120, 100%, 40%);">+ super().get_data(fields, key, value)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ self.csv_file.seek(0)</span><br><span style="color: hsl(120, 100%, 40%);">+ cr = csv.DictReader(self.csv_file)</span><br><span style="color: hsl(120, 100%, 40%);">+ if not cr:</span><br><span style="color: hsl(120, 100%, 40%);">+ raise RuntimeError("Could not open DictReader for CSV-File '%s'" % self.filename)</span><br><span style="color: hsl(120, 100%, 40%);">+ cr.fieldnames = [ field.upper() for field in cr.fieldnames ]</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ rc = {}</span><br><span style="color: hsl(120, 100%, 40%);">+ for row in cr:</span><br><span style="color: hsl(120, 100%, 40%);">+ if row[key] == value:</span><br><span style="color: hsl(120, 100%, 40%);">+ for f in fields:</span><br><span style="color: hsl(120, 100%, 40%);">+ if f in row:</span><br><span style="color: hsl(120, 100%, 40%);">+ rc.update({f : row[f]})</span><br><span style="color: hsl(120, 100%, 40%);">+ else:</span><br><span style="color: hsl(120, 100%, 40%);">+ raise RuntimeError("CSV-File '%s' lacks column '%s'" %</span><br><span style="color: hsl(120, 100%, 40%);">+ (self.filename, f))</span><br><span style="color: hsl(120, 100%, 40%);">+ return rc</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+def card_data_register(provider, provider_list=card_data_provider):</span><br><span style="color: hsl(120, 100%, 40%);">+ """Register a new card data provider"""</span><br><span style="color: hsl(120, 100%, 40%);">+ if not isinstance(provider, CardData):</span><br><span style="color: hsl(120, 100%, 40%);">+ raise ValueError("provider is not a card data provier")</span><br><span style="color: hsl(120, 100%, 40%);">+ provider_list.append(provider)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+def card_data_get(fields, key, value, provider_list=card_data_provider):</span><br><span style="color: hsl(120, 100%, 40%);">+ """Query all registered card data providers"""</span><br><span style="color: hsl(120, 100%, 40%);">+ for p in provider_list:</span><br><span style="color: hsl(120, 100%, 40%);">+ if not isinstance(p, CardData):</span><br><span style="color: hsl(120, 100%, 40%);">+ raise ValueError("provider list contains provider, which is not a card data provier")</span><br><span style="color: hsl(120, 100%, 40%);">+ result = p.get(fields, key, value)</span><br><span style="color: hsl(120, 100%, 40%);">+ if result:</span><br><span style="color: hsl(120, 100%, 40%);">+ return result</span><br><span style="color: hsl(120, 100%, 40%);">+ return {}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+def card_data_get_field(field, key, value, provider_list=card_data_provider):</span><br><span style="color: hsl(120, 100%, 40%);">+ """Query all registered card data providers for a single field"""</span><br><span style="color: hsl(120, 100%, 40%);">+ for p in provider_list:</span><br><span style="color: hsl(120, 100%, 40%);">+ if not isinstance(p, CardData):</span><br><span style="color: hsl(120, 100%, 40%);">+ raise ValueError("provider list contains provider, which is not a card data provier")</span><br><span style="color: hsl(120, 100%, 40%);">+ result = p.get_field(field, key, value)</span><br><span style="color: hsl(120, 100%, 40%);">+ if result:</span><br><span style="color: hsl(120, 100%, 40%);">+ return result</span><br><span style="color: hsl(120, 100%, 40%);">+ return None</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span></span><br></pre><p>To view, visit <a href="https://gerrit.osmocom.org/c/pysim/+/23538">change 23538</a>. To unsubscribe, or for help writing mail filters, visit <a href="https://gerrit.osmocom.org/settings">settings</a>.</p><div itemscope itemtype="http://schema.org/EmailMessage"><div itemscope itemprop="action" itemtype="http://schema.org/ViewAction"><link itemprop="url" href="https://gerrit.osmocom.org/c/pysim/+/23538"/><meta itemprop="name" content="View Change"/></div></div>
<div style="display:none"> Gerrit-Project: pysim </div>
<div style="display:none"> Gerrit-Branch: master </div>
<div style="display:none"> Gerrit-Change-Id: I51835ccb16bcbce35e7f3765e8927a4451509e77 </div>
<div style="display:none"> Gerrit-Change-Number: 23538 </div>
<div style="display:none"> Gerrit-PatchSet: 1 </div>
<div style="display:none"> Gerrit-Owner: dexter <pmaier@sysmocom.de> </div>
<div style="display:none"> Gerrit-MessageType: newchange </div>