blob: 00e32aa30c81b9b687ac62005861e0024d8d6051 [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 Weltec91085e2022-02-10 18:05:45 +010036card_key_providers = [] # type: List['CardKeyProvider']
37
Philipp Maier2b11c322021-03-17 12:37:39 +010038
Vadim Yanitskiy5ef16502021-05-02 01:52:56 +020039class CardKeyProvider(abc.ABC):
Harald Weltec91085e2022-02-10 18:05:45 +010040 """Base class, not containing any concrete implementation."""
Philipp Maier2b11c322021-03-17 12:37:39 +010041
Harald Weltec91085e2022-02-10 18:05:45 +010042 VALID_FIELD_NAMES = ['ICCID', 'ADM1',
43 'IMSI', 'PIN1', 'PIN2', 'PUK1', 'PUK2']
Philipp Maier2b11c322021-03-17 12:37:39 +010044
Harald Weltec91085e2022-02-10 18:05:45 +010045 # check input parameters, but do nothing concrete yet
46 def _verify_get_data(self, fields: List[str] = [], key: str = 'ICCID', value: str = "") -> Dict[str, str]:
47 """Verify multiple fields for identified card.
Philipp Maier2b11c322021-03-17 12:37:39 +010048
Harald Weltec91085e2022-02-10 18:05:45 +010049 Args:
50 fields : list of valid field names such as 'ADM1', 'PIN1', ... which are to be obtained
51 key : look-up key to identify card data, such as 'ICCID'
52 value : value for look-up key to identify card data
53 Returns:
54 dictionary of {field, value} strings for each requested field from 'fields'
55 """
56 for f in fields:
57 if (f not in self.VALID_FIELD_NAMES):
58 raise ValueError("Requested field name '%s' is not a valid field name, valid field names are: %s" %
59 (f, str(self.VALID_FIELD_NAMES)))
Philipp Maier2b11c322021-03-17 12:37:39 +010060
Harald Weltec91085e2022-02-10 18:05:45 +010061 if (key not in self.VALID_FIELD_NAMES):
62 raise ValueError("Key field name '%s' is not a valid field name, valid field names are: %s" %
63 (key, str(self.VALID_FIELD_NAMES)))
Philipp Maier2b11c322021-03-17 12:37:39 +010064
Harald Weltec91085e2022-02-10 18:05:45 +010065 return {}
Philipp Maier2b11c322021-03-17 12:37:39 +010066
Harald Weltec91085e2022-02-10 18:05:45 +010067 def get_field(self, field: str, key: str = 'ICCID', value: str = "") -> Optional[str]:
68 """get a single field from CSV file using a specified key/value pair"""
69 fields = [field]
70 result = self.get(fields, key, value)
71 return result.get(field)
Philipp Maier2b11c322021-03-17 12:37:39 +010072
Harald Weltec91085e2022-02-10 18:05:45 +010073 @abc.abstractmethod
74 def get(self, fields: List[str], key: str, value: str) -> Dict[str, str]:
75 """Get multiple card-individual fields for identified card.
Harald Welte9d0f1f02021-04-03 09:19:11 +020076
Harald Weltec91085e2022-02-10 18:05:45 +010077 Args:
78 fields : list of valid field names such as 'ADM1', 'PIN1', ... which are to be obtained
79 key : look-up key to identify card data, such as 'ICCID'
80 value : value for look-up key to identify card data
81 Returns:
82 dictionary of {field, value} strings for each requested field from 'fields'
83 """
84
Philipp Maier2b11c322021-03-17 12:37:39 +010085
Harald Welte4442b3d2021-04-03 09:00:16 +020086class CardKeyProviderCsv(CardKeyProvider):
Harald Weltec91085e2022-02-10 18:05:45 +010087 """Card key provider implementation that allows to query against a specified CSV file"""
88 csv_file = None
89 filename = None
Philipp Maier2b11c322021-03-17 12:37:39 +010090
Harald Weltec91085e2022-02-10 18:05:45 +010091 def __init__(self, filename: str):
92 """
93 Args:
94 filename : file name (path) of CSV file containing card-individual key/data
95 """
96 self.csv_file = open(filename, 'r')
97 if not self.csv_file:
98 raise RuntimeError("Could not open CSV file '%s'" % filename)
99 self.filename = filename
Philipp Maier2b11c322021-03-17 12:37:39 +0100100
Harald Weltec91085e2022-02-10 18:05:45 +0100101 def get(self, fields: List[str], key: str, value: str) -> Dict[str, str]:
102 super()._verify_get_data(fields, key, value)
Philipp Maier2b11c322021-03-17 12:37:39 +0100103
Harald Weltec91085e2022-02-10 18:05:45 +0100104 self.csv_file.seek(0)
105 cr = csv.DictReader(self.csv_file)
106 if not cr:
107 raise RuntimeError(
108 "Could not open DictReader for CSV-File '%s'" % self.filename)
109 cr.fieldnames = [field.upper() for field in cr.fieldnames]
Philipp Maier2b11c322021-03-17 12:37:39 +0100110
Harald Weltec91085e2022-02-10 18:05:45 +0100111 rc = {}
112 for row in cr:
113 if row[key] == value:
114 for f in fields:
115 if f in row:
116 rc.update({f: row[f]})
117 else:
118 raise RuntimeError("CSV-File '%s' lacks column '%s'" %
119 (self.filename, f))
120 return rc
Philipp Maier2b11c322021-03-17 12:37:39 +0100121
122
Harald Weltec91085e2022-02-10 18:05:45 +0100123def card_key_provider_register(provider: CardKeyProvider, provider_list=card_key_providers):
124 """Register a new card key provider.
Harald Welte9d0f1f02021-04-03 09:19:11 +0200125
Harald Weltec91085e2022-02-10 18:05:45 +0100126 Args:
127 provider : the to-be-registered provider
128 provider_list : override the list of providers from the global default
129 """
130 if not isinstance(provider, CardKeyProvider):
131 raise ValueError("provider is not a card data provier")
132 provider_list.append(provider)
Philipp Maier2b11c322021-03-17 12:37:39 +0100133
134
Harald Weltec91085e2022-02-10 18:05:45 +0100135def card_key_provider_get(fields, key: str, value: str, provider_list=card_key_providers) -> Dict[str, str]:
136 """Query all registered card data providers for card-individual [key] data.
Harald Welte9d0f1f02021-04-03 09:19:11 +0200137
Harald Weltec91085e2022-02-10 18:05:45 +0100138 Args:
139 fields : list of valid field names such as 'ADM1', 'PIN1', ... which are to be obtained
140 key : look-up key to identify card data, such as 'ICCID'
141 value : value for look-up key to identify card data
142 provider_list : override the list of providers from the global default
143 Returns:
144 dictionary of {field, value} strings for each requested field from 'fields'
145 """
146 for p in provider_list:
147 if not isinstance(p, CardKeyProvider):
148 raise ValueError(
149 "provider list contains element which is not a card data provier")
150 result = p.get(fields, key, value)
151 if result:
152 return result
153 return {}
Philipp Maier2b11c322021-03-17 12:37:39 +0100154
155
Harald Weltec91085e2022-02-10 18:05:45 +0100156def card_key_provider_get_field(field: str, key: str, value: str, provider_list=card_key_providers) -> Optional[str]:
157 """Query all registered card data providers for a single field.
Harald Welte9d0f1f02021-04-03 09:19:11 +0200158
Harald Weltec91085e2022-02-10 18:05:45 +0100159 Args:
160 field : name valid field such as 'ADM1', 'PIN1', ... which is to be obtained
161 key : look-up key to identify card data, such as 'ICCID'
162 value : value for look-up key to identify card data
163 provider_list : override the list of providers from the global default
164 Returns:
165 dictionary of {field, value} strings for the requested field
166 """
167 for p in provider_list:
168 if not isinstance(p, CardKeyProvider):
169 raise ValueError(
170 "provider list contains element which is not a card data provier")
171 result = p.get_field(field, key, value)
172 if result:
173 return result
174 return None