<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>