Harald Welte | 34eb504 | 2022-02-21 17:19:28 +0100 | [diff] [blame] | 1 | # coding=utf-8 |
| 2 | """Partial Support for GlobalPLatform Card Spec (currently 2.1.1) |
| 3 | |
| 4 | (C) 2022 by Harald Welte <laforge@osmocom.org> |
| 5 | |
| 6 | This program is free software: you can redistribute it and/or modify |
| 7 | it under the terms of the GNU General Public License as published by |
| 8 | the Free Software Foundation, either version 2 of the License, or |
| 9 | (at your option) any later version. |
| 10 | |
| 11 | This program is distributed in the hope that it will be useful, |
| 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 14 | GNU General Public License for more details. |
| 15 | |
| 16 | You should have received a copy of the GNU General Public License |
| 17 | along with this program. If not, see <http://www.gnu.org/licenses/>. |
| 18 | """ |
| 19 | |
| 20 | from typing import Optional, List, Dict, Tuple |
| 21 | from construct import Optional as COptional |
| 22 | from construct import * |
| 23 | from bidict import bidict |
| 24 | from pySim.construct import * |
| 25 | from pySim.utils import * |
| 26 | from pySim.filesystem import * |
| 27 | from pySim.tlv import * |
| 28 | from pySim.profile import CardProfile |
| 29 | |
| 30 | sw_table = { |
| 31 | 'Warnings': { |
| 32 | '6200': 'Logical Channel already closed', |
| 33 | '6283': 'Card Life Cycle State is CARD_LOCKED', |
| 34 | '6310': 'More data available', |
| 35 | }, |
| 36 | 'Execution errors': { |
| 37 | '6400': 'No specific diagnosis', |
| 38 | '6581': 'Memory failure', |
| 39 | }, |
| 40 | 'Checking errors': { |
| 41 | '6700': 'Wrong length in Lc', |
| 42 | }, |
| 43 | 'Functions in CLA not supported': { |
| 44 | '6881': 'Logical channel not supported or active', |
| 45 | '6882': 'Secure messaging not supported', |
| 46 | }, |
| 47 | 'Command not allowed': { |
| 48 | '6982': 'Security Status not satisfied', |
| 49 | '6985': 'Conditions of use not satisfied', |
| 50 | }, |
| 51 | 'Wrong parameters': { |
| 52 | '6a80': 'Incorrect values in command data', |
| 53 | '6a81': 'Function not supported e.g. card Life Cycle State is CARD_LOCKED', |
| 54 | '6a82': 'Application not found', |
| 55 | '6a84': 'Not enough memory space', |
| 56 | '6a86': 'Incorrect P1 P2', |
| 57 | '6a88': 'Referenced data not found', |
| 58 | }, |
| 59 | 'GlobalPlatform': { |
| 60 | '6d00': 'Invalid instruction', |
| 61 | '6e00': 'Invalid class', |
| 62 | }, |
| 63 | 'Application errors': { |
| 64 | '9484': 'Algorithm not supported', |
| 65 | '9485': 'Invalid key check value', |
| 66 | }, |
| 67 | } |
| 68 | |
| 69 | # GlobalPlatform 2.1.1 Section 9.1.6 |
| 70 | KeyType = Enum(Byte, des=0x80, |
| 71 | rsa_public_exponent_e_cleartex=0xA0, |
| 72 | rsa_modulus_n_cleartext=0xA1, |
| 73 | rsa_modulus_n=0xA2, |
| 74 | rsa_private_exponent_d=0xA3, |
| 75 | rsa_chines_remainder_p=0xA4, |
| 76 | rsa_chines_remainder_q=0xA5, |
| 77 | rsa_chines_remainder_pq=0xA6, |
| 78 | rsa_chines_remainder_dpi=0xA7, |
| 79 | rsa_chines_remainder_dqi=0xA8, |
| 80 | not_available=0xff) |
| 81 | |
| 82 | # GlobalPlatform 2.1.1 Section 9.3.3.1 |
| 83 | # example: |
| 84 | # e0 48 |
| 85 | # c0 04 01708010 |
| 86 | # c0 04 02708010 |
| 87 | # c0 04 03708010 |
| 88 | # c0 04 01018010 |
| 89 | # c0 04 02018010 |
| 90 | # c0 04 03018010 |
| 91 | # c0 04 01028010 |
| 92 | # c0 04 02028010 |
| 93 | # c0 04 03028010 |
| 94 | # c0 04 01038010 |
| 95 | # c0 04 02038010 |
| 96 | # c0 04 03038010 |
| 97 | class KeyInformationData(BER_TLV_IE, tag=0xc0): |
| 98 | _construct = Struct('key_identifier'/Byte, 'key_version_number'/Byte, |
| 99 | 'key_types'/GreedyRange(KeyType)) |
| 100 | class KeyInformation(BER_TLV_IE, tag=0xe0, nested=[KeyInformationData]): |
| 101 | pass |
| 102 | |
| 103 | # card data sample, returned in response to GET DATA (80ca006600): |
| 104 | # 66 31 |
| 105 | # 73 2f |
| 106 | # 06 07 |
| 107 | # 2a864886fc6b01 |
| 108 | # 60 0c |
| 109 | # 06 0a |
| 110 | # 2a864886fc6b02020101 |
| 111 | # 63 09 |
| 112 | # 06 07 |
| 113 | # 2a864886fc6b03 |
| 114 | # 64 0b |
| 115 | # 06 09 |
| 116 | # 2a864886fc6b040215 |
| 117 | |
| 118 | # GlobalPlatform 2.1.1 Table F-1 |
| 119 | class ObjectIdentifier(BER_TLV_IE, tag=0x06): |
| 120 | _construct = GreedyBytes |
| 121 | class CardManagementTypeAndVersion(BER_TLV_IE, tag=0x60, nested=[ObjectIdentifier]): |
| 122 | pass |
| 123 | class CardIdentificationScheme(BER_TLV_IE, tag=0x63, nested=[ObjectIdentifier]): |
| 124 | pass |
| 125 | class SecureChannelProtocolOfISD(BER_TLV_IE, tag=0x64, nested=[ObjectIdentifier]): |
| 126 | pass |
| 127 | class CardConfigurationDetails(BER_TLV_IE, tag=0x65): |
| 128 | _construct = GreedyBytes |
| 129 | class CardChipDetails(BER_TLV_IE, tag=0x66): |
| 130 | _construct = GreedyBytes |
| 131 | class CardRecognitionData(BER_TLV_IE, tag=0x73, nested=[ObjectIdentifier, |
| 132 | CardManagementTypeAndVersion, |
| 133 | CardIdentificationScheme, |
| 134 | SecureChannelProtocolOfISD, |
| 135 | CardConfigurationDetails, |
| 136 | CardChipDetails]): |
| 137 | pass |
| 138 | class CardData(BER_TLV_IE, tag=0x66, nested=[CardRecognitionData]): |
| 139 | pass |
| 140 | |
| 141 | # GlobalPlatform 2.1.1 Table F-2 |
| 142 | class SecureChannelProtocolOfSelectedSD(BER_TLV_IE, tag=0x64, nested=[ObjectIdentifier]): |
| 143 | pass |
| 144 | class SecurityDomainMgmtData(BER_TLV_IE, tag=0x73, nested=[CardManagementTypeAndVersion, |
| 145 | CardIdentificationScheme, |
| 146 | SecureChannelProtocolOfSelectedSD, |
| 147 | CardConfigurationDetails, |
| 148 | CardChipDetails]): |
| 149 | pass |
| 150 | |
| 151 | # GlobalPlatform 2.1.1 Section 9.1.1 |
| 152 | IsdLifeCycleState = Enum(Byte, op_ready=0x01, initialized=0x07, secured=0x0f, |
| 153 | card_locked = 0x7f, terminated=0xff) |
| 154 | |
| 155 | # GlobalPlatform 2.1.1 Section 9.9.3.1 |
| 156 | class ApplicationID(BER_TLV_IE, tag=0x84): |
| 157 | _construct = GreedyBytes |
| 158 | |
| 159 | # GlobalPlatform 2.1.1 Section 9.9.3.1 |
| 160 | class SecurityDomainManagementData(BER_TLV_IE, tag=0x73): |
| 161 | _construct = GreedyBytes |
| 162 | |
| 163 | # GlobalPlatform 2.1.1 Section 9.9.3.1 |
| 164 | class ApplicationProductionLifeCycleData(BER_TLV_IE, tag=0x9f6e): |
| 165 | _construct = GreedyBytes |
| 166 | |
| 167 | # GlobalPlatform 2.1.1 Section 9.9.3.1 |
| 168 | class MaximumLengthOfDataFieldInCommandMessage(BER_TLV_IE, tag=0x9f65): |
| 169 | _construct = GreedyInteger() |
| 170 | |
| 171 | # GlobalPlatform 2.1.1 Section 9.9.3.1 |
| 172 | class ProprietaryData(BER_TLV_IE, tag=0xA5, nested=[SecurityDomainManagementData, |
| 173 | ApplicationProductionLifeCycleData, |
| 174 | MaximumLengthOfDataFieldInCommandMessage]): |
| 175 | pass |
| 176 | |
| 177 | # GlobalPlatform 2.1.1 Section 9.9.3.1 |
| 178 | class FciTemplate(BER_TLV_IE, tag=0x6f, nested=[ApplicationID, SecurityDomainManagementData, |
| 179 | ApplicationProductionLifeCycleData, |
| 180 | MaximumLengthOfDataFieldInCommandMessage, |
| 181 | ProprietaryData]): |
| 182 | pass |
| 183 | |
| 184 | class IssuerIdentificationNumber(BER_TLV_IE, tag=0x42): |
| 185 | _construct = BcdAdapter(GreedyBytes) |
| 186 | |
| 187 | class CardImageNumber(BER_TLV_IE, tag=0x45): |
| 188 | _construct = BcdAdapter(GreedyBytes) |
| 189 | |
| 190 | class SequenceCounterOfDefaultKvn(BER_TLV_IE, tag=0xc1): |
| 191 | _construct = GreedyInteger() |
| 192 | |
| 193 | class ConfirmationCounter(BER_TLV_IE, tag=0xc2): |
| 194 | _construct = GreedyInteger() |
| 195 | |
| 196 | # Collection of all the data objects we can get from GET DATA |
| 197 | class DataCollection(TLV_IE_Collection, nested=[IssuerIdentificationNumber, |
| 198 | CardImageNumber, |
| 199 | CardData, |
| 200 | KeyInformation, |
| 201 | SequenceCounterOfDefaultKvn, |
| 202 | ConfirmationCounter]): |
| 203 | pass |
| 204 | |
| 205 | def decode_select_response(resp_hex: str) -> object: |
| 206 | t = FciTemplate() |
| 207 | t.from_tlv(h2b(resp_hex)) |
| 208 | d = t.to_dict() |
| 209 | return flatten_dict_lists(d['fci_template']) |
| 210 | |
| 211 | # Application Dedicated File of a Security Domain |
| 212 | class ADF_SD(CardADF): |
| 213 | def __init__(self, aid: str, name: str, desc: str): |
| 214 | super().__init__(aid=aid, fid=None, sfid=None, name=name, desc=desc) |
| 215 | self.shell_commands += [self.AddlShellCommands()] |
| 216 | |
| 217 | @staticmethod |
| 218 | def decode_select_response(res_hex: str) -> object: |
| 219 | return decode_select_response(res_hex) |
| 220 | |
| 221 | @with_default_category('Application-Specific Commands') |
| 222 | class AddlShellCommands(CommandSet): |
| 223 | def __init__(self): |
| 224 | super().__init__() |
| 225 | |
Harald Welte | fdcf3c5 | 2023-07-11 08:52:39 +0200 | [diff] [blame] | 226 | get_data_parser = argparse.ArgumentParser() |
| 227 | get_data_parser.add_argument('data_object_name', type=str, |
| 228 | help='Name of the data object to be retrieved from the card') |
| 229 | |
| 230 | @cmd2.with_argparser(get_data_parser) |
Harald Welte | 34eb504 | 2022-02-21 17:19:28 +0100 | [diff] [blame] | 231 | def do_get_data(self, opts): |
Harald Welte | fdcf3c5 | 2023-07-11 08:52:39 +0200 | [diff] [blame] | 232 | """Perform the GlobalPlatform GET DATA command in order to obtain some card-specific data.""" |
| 233 | tlv_cls_name = opts.data_object_name |
| 234 | try: |
| 235 | tlv_cls = DataCollection().members_by_name[tlv_cls_name] |
| 236 | except KeyError: |
| 237 | do_names = [camel_to_snake(str(x.__name__)) for x in DataCollection.possible_nested] |
| 238 | self._cmd.poutput('Unknown data object "%s", available options: %s' % (tlv_cls_name, |
| 239 | do_names)) |
| 240 | return |
Harald Welte | 4625512 | 2023-10-21 23:40:42 +0200 | [diff] [blame] | 241 | (data, sw) = self._cmd.lchan.scc.get_data(cla=0x80, tag=tlv_cls.tag) |
Harald Welte | 34eb504 | 2022-02-21 17:19:28 +0100 | [diff] [blame] | 242 | ie = tlv_cls() |
| 243 | ie.from_tlv(h2b(data)) |
| 244 | self._cmd.poutput_json(ie.to_dict()) |
| 245 | |
| 246 | def complete_get_data(self, text, line, begidx, endidx) -> List[str]: |
Harald Welte | 30de9fd | 2023-07-09 21:25:14 +0200 | [diff] [blame] | 247 | data_dict = {camel_to_snake(str(x.__name__)): x for x in DataCollection.possible_nested} |
Harald Welte | 34eb504 | 2022-02-21 17:19:28 +0100 | [diff] [blame] | 248 | index_dict = {1: data_dict} |
| 249 | return self._cmd.index_based_complete(text, line, begidx, endidx, index_dict=index_dict) |
| 250 | |
| 251 | # Card Application of a Security Domain |
| 252 | class CardApplicationSD(CardApplication): |
| 253 | def __init__(self, aid: str, name: str, desc: str): |
| 254 | super().__init__(name, adf=ADF_SD(aid, name, desc), sw=sw_table) |
| 255 | |
| 256 | # Card Application of Issuer Security Domain |
| 257 | class CardApplicationISD(CardApplicationSD): |
| 258 | # FIXME: ISD AID is not static, but could be different. One can select the empty |
| 259 | # application using '00a4040000' and then parse the response FCI to get the ISD AID |
| 260 | def __init__(self, aid='a000000003000000'): |
| 261 | super().__init__(aid=aid, name='ADF.ISD', desc='Issuer Security Domain') |
| 262 | |
| 263 | #class CardProfileGlobalPlatform(CardProfile): |
| 264 | # ORDER = 23 |
| 265 | # |
| 266 | # def __init__(self, name='GlobalPlatform'): |
| 267 | # super().__init__(name, desc='GlobalPlatfomr 2.1.1', cla=['00','80','84'], sw=sw_table) |