blob: 05de0be5bd7a4671e79ff2c558c12e4953649e96 [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
33import csv
34
Harald Welte9d0f1f02021-04-03 09:19:11 +020035card_key_providers = [] # type: List['CardKeyProvider']
Philipp Maier2b11c322021-03-17 12:37:39 +010036
Harald Welte4442b3d2021-04-03 09:00:16 +020037class CardKeyProvider(object):
Harald Welte9d0f1f02021-04-03 09:19:11 +020038 """Base class, not containing any concrete implementation."""
Philipp Maier2b11c322021-03-17 12:37:39 +010039
Philipp Maier46f09af2021-03-25 20:24:27 +010040 VALID_FIELD_NAMES = ['ICCID', 'ADM1', 'IMSI', 'PIN1', 'PIN2', 'PUK1', 'PUK2']
Philipp Maier2b11c322021-03-17 12:37:39 +010041
42 # check input parameters, but do nothing concrete yet
Harald Welte9d0f1f02021-04-03 09:19:11 +020043 def _verify_get_data(self, fields:List[str]=[], key:str='ICCID', value:str="") -> Dict[str,str]:
44 """Verify multiple fields for identified card.
Philipp Maier2b11c322021-03-17 12:37:39 +010045
Harald Welte9d0f1f02021-04-03 09:19:11 +020046 Args:
47 fields : list of valid field names such as 'ADM1', 'PIN1', ... which are to be obtained
48 key : look-up key to identify card data, such as 'ICCID'
49 value : value for look-up key to identify card data
50 Returns:
51 dictionary of {field, value} strings for each requested field from 'fields'
52 """
Philipp Maier2b11c322021-03-17 12:37:39 +010053 for f in fields:
54 if (f not in self.VALID_FIELD_NAMES):
55 raise ValueError("Requested field name '%s' is not a valid field name, valid field names are: %s" %
56 (f, str(self.VALID_FIELD_NAMES)))
57
58 if (key not in self.VALID_FIELD_NAMES):
59 raise ValueError("Key field name '%s' is not a valid field name, valid field names are: %s" %
60 (key, str(self.VALID_FIELD_NAMES)))
61
62 return {}
63
Harald Welte90d3b972021-04-03 08:56:21 +020064 def get_field(self, field:str, key:str='ICCID', value:str="") -> Optional[str]:
Philipp Maier2b11c322021-03-17 12:37:39 +010065 """get a single field from CSV file using a specified key/value pair"""
66 fields = [field]
67 result = self.get(fields, key, value)
68 return result.get(field)
69
Harald Welte90d3b972021-04-03 08:56:21 +020070 def get(self, fields:List[str], key:str, value:str) -> Dict[str,str]:
Harald Welte9d0f1f02021-04-03 09:19:11 +020071 """Get multiple card-individual fields for identified card.
72
73 Args:
74 fields : list of valid field names such as 'ADM1', 'PIN1', ... which are to be obtained
75 key : look-up key to identify card data, such as 'ICCID'
76 value : value for look-up key to identify card data
77 Returns:
78 dictionary of {field, value} strings for each requested field from 'fields'
79 """
Harald Welte90d3b972021-04-03 08:56:21 +020080 pass
Philipp Maier2b11c322021-03-17 12:37:39 +010081
Harald Welte4442b3d2021-04-03 09:00:16 +020082class CardKeyProviderCsv(CardKeyProvider):
Harald Welte9d0f1f02021-04-03 09:19:11 +020083 """Card key provider implementation that allows to query against a specified CSV file"""
Philipp Maier2b11c322021-03-17 12:37:39 +010084 csv_file = None
85 filename = None
86
Harald Welte90d3b972021-04-03 08:56:21 +020087 def __init__(self, filename:str):
Harald Welte9d0f1f02021-04-03 09:19:11 +020088 """
89 Args:
90 filename : file name (path) of CSV file containing card-individual key/data
91 """
Philipp Maier2b11c322021-03-17 12:37:39 +010092 self.csv_file = open(filename, 'r')
93 if not self.csv_file:
Harald Welte9d0f1f02021-04-03 09:19:11 +020094 raise RuntimeError("Could not open CSV file '%s'" % filename)
Philipp Maier2b11c322021-03-17 12:37:39 +010095 self.filename = filename
96
Harald Welte90d3b972021-04-03 08:56:21 +020097 def get(self, fields:List[str], key:str, value:str) -> Dict[str,str]:
Harald Welte9d0f1f02021-04-03 09:19:11 +020098 super()._verify_get_data(fields, key, value)
Philipp Maier2b11c322021-03-17 12:37:39 +010099
100 self.csv_file.seek(0)
101 cr = csv.DictReader(self.csv_file)
102 if not cr:
103 raise RuntimeError("Could not open DictReader for CSV-File '%s'" % self.filename)
104 cr.fieldnames = [ field.upper() for field in cr.fieldnames ]
105
106 rc = {}
107 for row in cr:
108 if row[key] == value:
109 for f in fields:
110 if f in row:
111 rc.update({f : row[f]})
112 else:
113 raise RuntimeError("CSV-File '%s' lacks column '%s'" %
114 (self.filename, f))
115 return rc
116
117
Harald Welte4442b3d2021-04-03 09:00:16 +0200118def card_key_provider_register(provider:CardKeyProvider, provider_list=card_key_providers):
Harald Welte9d0f1f02021-04-03 09:19:11 +0200119 """Register a new card key provider.
120
121 Args:
122 provider : the to-be-registered provider
123 provider_list : override the list of providers from the global default
124 """
Harald Welte4442b3d2021-04-03 09:00:16 +0200125 if not isinstance(provider, CardKeyProvider):
Philipp Maier2b11c322021-03-17 12:37:39 +0100126 raise ValueError("provider is not a card data provier")
127 provider_list.append(provider)
128
129
Harald Welte4442b3d2021-04-03 09:00:16 +0200130def card_key_provider_get(fields, key:str, value:str, provider_list=card_key_providers) -> Dict[str,str]:
Harald Welte9d0f1f02021-04-03 09:19:11 +0200131 """Query all registered card data providers for card-individual [key] data.
132
133 Args:
134 fields : list of valid field names such as 'ADM1', 'PIN1', ... which are to be obtained
135 key : look-up key to identify card data, such as 'ICCID'
136 value : value for look-up key to identify card data
137 provider_list : override the list of providers from the global default
138 Returns:
139 dictionary of {field, value} strings for each requested field from 'fields'
140 """
Philipp Maier2b11c322021-03-17 12:37:39 +0100141 for p in provider_list:
Harald Welte4442b3d2021-04-03 09:00:16 +0200142 if not isinstance(p, CardKeyProvider):
Harald Welte9d0f1f02021-04-03 09:19:11 +0200143 raise ValueError("provider list contains element which is not a card data provier")
Philipp Maier2b11c322021-03-17 12:37:39 +0100144 result = p.get(fields, key, value)
145 if result:
146 return result
147 return {}
148
149
Harald Welte4442b3d2021-04-03 09:00:16 +0200150def 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 +0200151 """Query all registered card data providers for a single field.
152
153 Args:
154 field : name valid field such as 'ADM1', 'PIN1', ... which is to be obtained
155 key : look-up key to identify card data, such as 'ICCID'
156 value : value for look-up key to identify card data
157 provider_list : override the list of providers from the global default
158 Returns:
159 dictionary of {field, value} strings for the requested field
160 """
Philipp Maier2b11c322021-03-17 12:37:39 +0100161 for p in provider_list:
Harald Welte4442b3d2021-04-03 09:00:16 +0200162 if not isinstance(p, CardKeyProvider):
Harald Welte9d0f1f02021-04-03 09:19:11 +0200163 raise ValueError("provider list contains element which is not a card data provier")
Philipp Maier2b11c322021-03-17 12:37:39 +0100164 result = p.get_field(field, key, value)
165 if result:
166 return result
167 return None