blob: fc8afd941159d6ecc939ce30ff13cf87cfd8c3c3 [file] [log] [blame]
Harald Weltec781ab82021-05-23 11:50:19 +02001#!/usr/bin/env python3
2
3# RESTful HTTP service for performing authentication against USIM cards
4#
Harald Welte04897d52022-07-23 14:07:00 +02005# (C) 2021-2022 by Harald Welte <laforge@osmocom.org>
Harald Weltec781ab82021-05-23 11:50:19 +02006#
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as published by
9# the Free Software Foundation, either version 2 of the License, or
10# (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19
20import json
21import sys
22import argparse
23
Harald Welte3f3b45a2022-07-23 13:44:20 +020024from klein import Klein
Harald Weltec781ab82021-05-23 11:50:19 +020025
26from pySim.transport import ApduTracer
27from pySim.transport.pcsc import PcscSimLink
28from pySim.commands import SimCardCommands
Philipp Maier91b379a2023-08-16 11:43:19 +020029from pySim.cards import UiccCardBase
Philipp Maier7d138452023-08-16 11:47:36 +020030from pySim.utils import dec_iccid, dec_imsi
31from pySim.ts_51_011 import EF_IMSI
32from pySim.ts_102_221 import EF_ICCID
Harald Weltec781ab82021-05-23 11:50:19 +020033from pySim.exceptions import *
34
35class ApduPrintTracer(ApduTracer):
36 def trace_response(self, cmd, sw, resp):
37 #print("CMD: %s -> RSP: %s %s" % (cmd, sw, resp))
38 pass
39
Harald Welte33f8da82021-11-03 12:14:50 +010040def connect_to_card(slot_nr:int):
41 tp = PcscSimLink(slot_nr, apdu_tracer=ApduPrintTracer())
42 tp.connect()
43
44 scc = SimCardCommands(tp)
Philipp Maier91b379a2023-08-16 11:43:19 +020045 card = UiccCardBase(scc)
Harald Welte33f8da82021-11-03 12:14:50 +010046
47 # this should be part of UsimCard, but FairewavesSIM breaks with that :/
48 scc.cla_byte = "00"
49 scc.sel_ctrl = "0004"
50
51 card.read_aids()
Philipp Maier71a3fb82023-08-16 11:31:31 +020052
53 # ensure that MF is selected when we are done.
54 card._scc.select_file('3f00')
Harald Welte33f8da82021-11-03 12:14:50 +010055
56 return tp, scc, card
57
Harald Welte3f3b45a2022-07-23 13:44:20 +020058class ApiError:
Harald Welte04897d52022-07-23 14:07:00 +020059 def __init__(self, msg:str, sw=None):
Harald Welte3f3b45a2022-07-23 13:44:20 +020060 self.msg = msg
Harald Welte04897d52022-07-23 14:07:00 +020061 self.sw = sw
Harald Welte3f3b45a2022-07-23 13:44:20 +020062
63 def __str__(self):
Harald Welte04897d52022-07-23 14:07:00 +020064 d = {'error': {'message':self.msg}}
65 if self.sw:
66 d['error']['status_word'] = self.sw
67 return json.dumps(d)
Harald Welte3f3b45a2022-07-23 13:44:20 +020068
69
Harald Welte6f8cf9b2022-07-06 16:28:56 +020070def set_headers(request):
71 request.setHeader('Content-Type', 'application/json')
Harald Welte33f8da82021-11-03 12:14:50 +010072
Harald Welte3f3b45a2022-07-23 13:44:20 +020073class SimRestServer:
74 app = Klein()
Harald Weltec781ab82021-05-23 11:50:19 +020075
Harald Welte3f3b45a2022-07-23 13:44:20 +020076 @app.handle_errors(NoCardError)
77 def no_card_error(self, request, failure):
78 set_headers(request)
Harald Weltec781ab82021-05-23 11:50:19 +020079 request.setResponseCode(410)
Harald Welte3f3b45a2022-07-23 13:44:20 +020080 return str(ApiError("No SIM card inserted in slot"))
Harald Weltec781ab82021-05-23 11:50:19 +020081
Harald Welte3f3b45a2022-07-23 13:44:20 +020082 @app.handle_errors(ReaderError)
83 def reader_error(self, request, failure):
84 set_headers(request)
85 request.setResponseCode(404)
86 return str(ApiError("Reader Error: Specified SIM Slot doesn't exist"))
87
88 @app.handle_errors(ProtocolError)
89 def protocol_error(self, request, failure):
90 set_headers(request)
91 request.setResponseCode(500)
Harald Welte04897d52022-07-23 14:07:00 +020092 return str(ApiError("Protocol Error: %s" % failure.value))
Harald Welte3f3b45a2022-07-23 13:44:20 +020093
94 @app.handle_errors(SwMatchError)
95 def sw_match_error(self, request, failure):
96 set_headers(request)
97 request.setResponseCode(500)
Harald Welte04897d52022-07-23 14:07:00 +020098 sw = failure.value.sw_actual
99 if sw == '9862':
100 return str(ApiError("Card Authentication Error - Incorrect MAC", sw))
101 elif sw == '6982':
102 return str(ApiError("Security Status not satisfied - Card PIN enabled?", sw))
103 else:
Philipp Maier3a37ad02023-08-16 12:08:56 +0200104 return str(ApiError("Card Communication Error %s" % failure.value, sw))
Harald Welte3f3b45a2022-07-23 13:44:20 +0200105
106
107 @app.route('/sim-auth-api/v1/slot/<int:slot>')
108 def auth(self, request, slot):
109 """REST API endpoint for performing authentication against a USIM.
110 Expects a JSON body containing RAND and AUTN.
111 Returns a JSON body containing RES, CK, IK and Kc."""
112 try:
113 # there are two hex-string JSON parameters in the body: rand and autn
114 content = json.loads(request.content.read())
115 rand = content['rand']
116 autn = content['autn']
117 except:
118 set_headers(request)
119 request.setResponseCode(400)
120 return str(ApiError("Malformed Request"))
121
122 tp, scc, card = connect_to_card(slot)
123
Harald Weltec781ab82021-05-23 11:50:19 +0200124 card.select_adf_by_aid(adf='usim')
125 res, sw = scc.authenticate(rand, autn)
Harald Weltec781ab82021-05-23 11:50:19 +0200126
Harald Welte3f3b45a2022-07-23 13:44:20 +0200127 tp.disconnect()
Harald Weltec781ab82021-05-23 11:50:19 +0200128
Harald Welte3f3b45a2022-07-23 13:44:20 +0200129 set_headers(request)
130 return json.dumps(res, indent=4)
Harald Weltec781ab82021-05-23 11:50:19 +0200131
Harald Welte3f3b45a2022-07-23 13:44:20 +0200132 @app.route('/sim-info-api/v1/slot/<int:slot>')
133 def info(self, request, slot):
134 """REST API endpoint for obtaining information about an USIM.
135 Expects empty body in request.
136 Returns a JSON body containing ICCID, IMSI."""
Harald Welte33f8da82021-11-03 12:14:50 +0100137
Harald Welte33f8da82021-11-03 12:14:50 +0100138 tp, scc, card = connect_to_card(slot)
Harald Welte33f8da82021-11-03 12:14:50 +0100139
Philipp Maier7d138452023-08-16 11:47:36 +0200140 ef_iccid = EF_ICCID()
141 (iccid, sw) = card._scc.read_binary(ef_iccid.fid)
142
Harald Welte33f8da82021-11-03 12:14:50 +0100143 card.select_adf_by_aid(adf='usim')
Philipp Maier7d138452023-08-16 11:47:36 +0200144 ef_imsi = EF_IMSI()
145 (imsi, sw) = card._scc.read_binary(ef_imsi.fid)
146
147 res = {"imsi": dec_imsi(imsi), "iccid": dec_iccid(iccid) }
Harald Welte33f8da82021-11-03 12:14:50 +0100148
Harald Welte3f3b45a2022-07-23 13:44:20 +0200149 tp.disconnect()
Harald Welte33f8da82021-11-03 12:14:50 +0100150
Harald Welte3f3b45a2022-07-23 13:44:20 +0200151 set_headers(request)
152 return json.dumps(res, indent=4)
Harald Welte33f8da82021-11-03 12:14:50 +0100153
154
Harald Weltec781ab82021-05-23 11:50:19 +0200155def main(argv):
156 parser = argparse.ArgumentParser()
157 parser.add_argument("-H", "--host", help="Host/IP to bind HTTP to", default="localhost")
158 parser.add_argument("-p", "--port", help="TCP port to bind HTTP to", default=8000)
159 #parser.add_argument("-v", "--verbose", help="increase output verbosity", action='count', default=0)
160
161 args = parser.parse_args()
162
Harald Welte3f3b45a2022-07-23 13:44:20 +0200163 srr = SimRestServer()
164 srr.app.run(args.host, args.port)
Harald Weltec781ab82021-05-23 11:50:19 +0200165
166if __name__ == "__main__":
167 main(sys.argv)