blob: 67b0b28b77c0c27ebcdc7d213d3ccbb567725865 [file] [log] [blame]
Philipp Maier2b11c322021-03-17 12:37:39 +01001# coding=utf-8
Harald Welte9d0f1f02021-04-03 09:19:11 +02002"""Obtaining card parameters (mostly key data) from external source.
3
4This module contains a base class and a concrete implementation of
5obtaining card key material (or other card-individual parameters) from
6an external data source.
7
8This is used e.g. to keep PIN/PUK data in some file on disk, avoiding
9the need of manually entering the related card-individual data on every
10operation with pySim-shell.
11"""
Philipp Maier2b11c322021-03-17 12:37:39 +010012
Harald Welte90d3b972021-04-03 08:56:21 +020013# (C) 2021 by Sysmocom s.f.m.c. GmbH
14# All Rights Reserved
15#
16# Author: Philipp Maier
17#
18# This program is free software: you can redistribute it and/or modify
19# it under the terms of the GNU General Public License as published by
20# the Free Software Foundation, either version 2 of the License, or
21# (at your option) any later version.
22#
23# This program is distributed in the hope that it will be useful,
24# but WITHOUT ANY WARRANTY; without even the implied warranty of
25# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26# GNU General Public License for more details.
27#
28# You should have received a copy of the GNU General Public License
29# along with this program. If not, see <http://www.gnu.org/licenses/>.
Philipp Maier2b11c322021-03-17 12:37:39 +010030
Harald Welte90d3b972021-04-03 08:56:21 +020031from typing import List, Dict, Optional
Philipp Maier2b11c322021-03-17 12:37:39 +010032
Vadim Yanitskiy5ef16502021-05-02 01:52:56 +020033import abc
Philipp Maier2b11c322021-03-17 12:37:39 +010034import csv
35
Harald Welte9d0f1f02021-04-03 09:19:11 +020036card_key_providers = [] # type: List['CardKeyProvider']
Philipp Maier2b11c322021-03-17 12:37:39 +010037
Vadim Yanitskiy5ef16502021-05-02 01:52:56 +020038class CardKeyProvider(abc.ABC):
Harald Welte9d0f1f02021-04-03 09:19:11 +020039 """Base class, not containing any concrete implementation."""
Philipp Maier2b11c322021-03-17 12:37:39 +010040
Philipp Maier46f09af2021-03-25 20:24:27 +010041 VALID_FIELD_NAMES = ['ICCID', 'ADM1', 'IMSI', 'PIN1', 'PIN2', 'PUK1', 'PUK2']
Philipp Maier2b11c322021-03-17 12:37:39 +010042
43 # check input parameters, but do nothing concrete yet
Harald Welte9d0f1f02021-04-03 09:19:11 +020044 def _verify_get_data(self, fields:List[str]=[], key:str='ICCID', value:str="") -> Dict[str,str]:
45 """Verify multiple fields for identified card.
Philipp Maier2b11c322021-03-17 12:37:39 +010046
Harald Welte9d0f1f02021-04-03 09:19:11 +020047 Args:
48 fields : list of valid field names such as 'ADM1', 'PIN1', ... which are to be obtained
49 key : look-up key to identify card data, such as 'ICCID'
50 value : value for look-up key to identify card data
51 Returns:
52 dictionary of {field, value} strings for each requested field from 'fields'
53 """
Philipp Maier2b11c322021-03-17 12:37:39 +010054 for f in fields:
55 if (f not in self.VALID_FIELD_NAMES):
56 raise ValueError("Requested field name '%s' is not a valid field name, valid field names are: %s" %
57 (f, str(self.VALID_FIELD_NAMES)))
58
59 if (key not in self.VALID_FIELD_NAMES):
60 raise ValueError("Key field name '%s' is not a valid field name, valid field names are: %s" %
61 (key, str(self.VALID_FIELD_NAMES)))
62
63 return {}
64
Harald Welte90d3b972021-04-03 08:56:21 +020065 def get_field(self, field:str, key:str='ICCID', value:str="") -> Optional[str]:
Philipp Maier2b11c322021-03-17 12:37:39 +010066 """get a single field from CSV file using a specified key/value pair"""
67 fields = [field]
68 result = self.get(fields, key, value)
69 return result.get(field)
70
Vadim Yanitskiy5ef16502021-05-02 01:52:56 +020071 @abc.abstractmethod
Harald Welte90d3b972021-04-03 08:56:21 +020072 def get(self, fields:List[str], key:str, value:str) -> Dict[str,str]:
Harald Welte9d0f1f02021-04-03 09:19:11 +020073 """Get multiple card-individual fields for identified card.
74
75 Args:
76 fields : list of valid field names such as 'ADM1', 'PIN1', ... which are to be obtained
77 key : look-up key to identify card data, such as 'ICCID'
78 value : value for look-up key to identify card data
79 Returns:
80 dictionary of {field, value} strings for each requested field from 'fields'
81 """
Philipp Maier2b11c322021-03-17 12:37:39 +010082
Harald Welte4442b3d2021-04-03 09:00:16 +020083class CardKeyProviderCsv(CardKeyProvider):
Harald Welte9d0f1f02021-04-03 09:19:11 +020084 """Card key provider implementation that allows to query against a specified CSV file"""
Philipp Maier2b11c322021-03-17 12:37:39 +010085 csv_file = None
86 filename = None
87
Harald Welte90d3b972021-04-03 08:56:21 +020088 def __init__(self, filename:str):
Harald Welte9d0f1f02021-04-03 09:19:11 +020089 """
90 Args:
91 filename : file name (path) of CSV file containing card-individual key/data
92 """
Philipp Maier2b11c322021-03-17 12:37:39 +010093 self.csv_file = open(filename, 'r')
94 if not self.csv_file:
Harald Welte9d0f1f02021-04-03 09:19:11 +020095 raise RuntimeError("Could not open CSV file '%s'" % filename)
Philipp Maier2b11c322021-03-17 12:37:39 +010096 self.filename = filename
97
Harald Welte90d3b972021-04-03 08:56:21 +020098 def get(self, fields:List[str], key:str, value:str) -> Dict[str,str]:
Harald Welte9d0f1f02021-04-03 09:19:11 +020099 super()._verify_get_data(fields, key, value)
Philipp Maier2b11c322021-03-17 12:37:39 +0100100
101 self.csv_file.seek(0)
102 cr = csv.DictReader(self.csv_file)
103 if not cr:
104 raise RuntimeError("Could not open DictReader for CSV-File '%s'" % self.filename)
105 cr.fieldnames = [ field.upper() for field in cr.fieldnames ]
106
107 rc = {}
108 for row in cr:
109 if row[key] == value:
110 for f in fields:
111 if f in row:
112 rc.update({f : row[f]})
113 else:
114 raise RuntimeError("CSV-File '%s' lacks column '%s'" %
115 (self.filename, f))
116 return rc
117
118
Harald Welte4442b3d2021-04-03 09:00:16 +0200119def card_key_provider_register(provider:CardKeyProvider, provider_list=card_key_providers):
Harald Welte9d0f1f02021-04-03 09:19:11 +0200120 """Register a new card key provider.
121
122 Args:
123 provider : the to-be-registered provider
124 provider_list : override the list of providers from the global default
125 """
Harald Welte4442b3d2021-04-03 09:00:16 +0200126 if not isinstance(provider, CardKeyProvider):
Philipp Maier2b11c322021-03-17 12:37:39 +0100127 raise ValueError("provider is not a card data provier")
128 provider_list.append(provider)
129
130
Harald Welte4442b3d2021-04-03 09:00:16 +0200131def card_key_provider_get(fields, key:str, value:str, provider_list=card_key_providers) -> Dict[str,str]:
Harald Welte9d0f1f02021-04-03 09:19:11 +0200132 """Query all registered card data providers for card-individual [key] data.
133
134 Args:
135 fields : list of valid field names such as 'ADM1', 'PIN1', ... which are to be obtained
136 key : look-up key to identify card data, such as 'ICCID'
137 value : value for look-up key to identify card data
138 provider_list : override the list of providers from the global default
139 Returns:
140 dictionary of {field, value} strings for each requested field from 'fields'
141 """
Philipp Maier2b11c322021-03-17 12:37:39 +0100142 for p in provider_list:
Harald Welte4442b3d2021-04-03 09:00:16 +0200143 if not isinstance(p, CardKeyProvider):
Harald Welte9d0f1f02021-04-03 09:19:11 +0200144 raise ValueError("provider list contains element which is not a card data provier")
Philipp Maier2b11c322021-03-17 12:37:39 +0100145 result = p.get(fields, key, value)
146 if result:
147 return result
148 return {}
149
150
Harald Welte4442b3d2021-04-03 09:00:16 +0200151def card_key_provider_get_field(field:str, key:str, value:str, provider_list=card_key_providers) -> Optional[str]:
Harald Welte9d0f1f02021-04-03 09:19:11 +0200152 """Query all registered card data providers for a single field.
153
154 Args:
155 field : name valid field such as 'ADM1', 'PIN1', ... which is to be obtained
156 key : look-up key to identify card data, such as 'ICCID'
157 value : value for look-up key to identify card data
158 provider_list : override the list of providers from the global default
159 Returns:
160 dictionary of {field, value} strings for the requested field
161 """
Philipp Maier2b11c322021-03-17 12:37:39 +0100162 for p in provider_list:
Harald Welte4442b3d2021-04-03 09:00:16 +0200163 if not isinstance(p, CardKeyProvider):
Harald Welte9d0f1f02021-04-03 09:19:11 +0200164 raise ValueError("provider list contains element which is not a card data provier")
Philipp Maier2b11c322021-03-17 12:37:39 +0100165 result = p.get_field(field, key, value)
166 if result:
167 return result
168 return None