blob: 62498b45c7221767c84ae6119c02a3d5f765e082 [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#
5# (C) 2021 by Harald Welte <laforge@osmocom.org>
6#
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
29from pySim.cards import UsimCard
30from pySim.exceptions import *
31
32class ApduPrintTracer(ApduTracer):
33 def trace_response(self, cmd, sw, resp):
34 #print("CMD: %s -> RSP: %s %s" % (cmd, sw, resp))
35 pass
36
Harald Welte33f8da82021-11-03 12:14:50 +010037def connect_to_card(slot_nr:int):
38 tp = PcscSimLink(slot_nr, apdu_tracer=ApduPrintTracer())
39 tp.connect()
40
41 scc = SimCardCommands(tp)
42 card = UsimCard(scc)
43
44 # this should be part of UsimCard, but FairewavesSIM breaks with that :/
45 scc.cla_byte = "00"
46 scc.sel_ctrl = "0004"
47
48 card.read_aids()
49 card.select_adf_by_aid(adf='usim')
50
51 return tp, scc, card
52
Harald Welte3f3b45a2022-07-23 13:44:20 +020053class ApiError:
54 def __init__(self, msg:str):
55 self.msg = msg
56
57 def __str__(self):
58 return json.dumps({'error': {'message':self.msg}})
59
60
Harald Welte6f8cf9b2022-07-06 16:28:56 +020061def set_headers(request):
62 request.setHeader('Content-Type', 'application/json')
Harald Welte33f8da82021-11-03 12:14:50 +010063
Harald Welte3f3b45a2022-07-23 13:44:20 +020064class SimRestServer:
65 app = Klein()
Harald Weltec781ab82021-05-23 11:50:19 +020066
Harald Welte3f3b45a2022-07-23 13:44:20 +020067 @app.handle_errors(NoCardError)
68 def no_card_error(self, request, failure):
69 set_headers(request)
Harald Weltec781ab82021-05-23 11:50:19 +020070 request.setResponseCode(410)
Harald Welte3f3b45a2022-07-23 13:44:20 +020071 return str(ApiError("No SIM card inserted in slot"))
Harald Weltec781ab82021-05-23 11:50:19 +020072
Harald Welte3f3b45a2022-07-23 13:44:20 +020073 @app.handle_errors(ReaderError)
74 def reader_error(self, request, failure):
75 set_headers(request)
76 request.setResponseCode(404)
77 return str(ApiError("Reader Error: Specified SIM Slot doesn't exist"))
78
79 @app.handle_errors(ProtocolError)
80 def protocol_error(self, request, failure):
81 set_headers(request)
82 request.setResponseCode(500)
83 return str(ApiError("Protocol Error"))
84
85 @app.handle_errors(SwMatchError)
86 def sw_match_error(self, request, failure):
87 set_headers(request)
88 request.setResponseCode(500)
89 return str(ApiError("Card Communication Error %s" % failure))
90
91
92 @app.route('/sim-auth-api/v1/slot/<int:slot>')
93 def auth(self, request, slot):
94 """REST API endpoint for performing authentication against a USIM.
95 Expects a JSON body containing RAND and AUTN.
96 Returns a JSON body containing RES, CK, IK and Kc."""
97 try:
98 # there are two hex-string JSON parameters in the body: rand and autn
99 content = json.loads(request.content.read())
100 rand = content['rand']
101 autn = content['autn']
102 except:
103 set_headers(request)
104 request.setResponseCode(400)
105 return str(ApiError("Malformed Request"))
106
107 tp, scc, card = connect_to_card(slot)
108
Harald Weltec781ab82021-05-23 11:50:19 +0200109 card.select_adf_by_aid(adf='usim')
110 res, sw = scc.authenticate(rand, autn)
Harald Weltec781ab82021-05-23 11:50:19 +0200111
Harald Welte3f3b45a2022-07-23 13:44:20 +0200112 tp.disconnect()
Harald Weltec781ab82021-05-23 11:50:19 +0200113
Harald Welte3f3b45a2022-07-23 13:44:20 +0200114 set_headers(request)
115 return json.dumps(res, indent=4)
Harald Weltec781ab82021-05-23 11:50:19 +0200116
Harald Welte3f3b45a2022-07-23 13:44:20 +0200117 @app.route('/sim-info-api/v1/slot/<int:slot>')
118 def info(self, request, slot):
119 """REST API endpoint for obtaining information about an USIM.
120 Expects empty body in request.
121 Returns a JSON body containing ICCID, IMSI."""
Harald Welte33f8da82021-11-03 12:14:50 +0100122
Harald Welte33f8da82021-11-03 12:14:50 +0100123 tp, scc, card = connect_to_card(slot)
Harald Welte33f8da82021-11-03 12:14:50 +0100124
Harald Welte33f8da82021-11-03 12:14:50 +0100125 card.select_adf_by_aid(adf='usim')
126 iccid, sw = card.read_iccid()
127 imsi, sw = card.read_imsi()
128 res = {"imsi": imsi, "iccid": iccid }
Harald Welte33f8da82021-11-03 12:14:50 +0100129
Harald Welte3f3b45a2022-07-23 13:44:20 +0200130 tp.disconnect()
Harald Welte33f8da82021-11-03 12:14:50 +0100131
Harald Welte3f3b45a2022-07-23 13:44:20 +0200132 set_headers(request)
133 return json.dumps(res, indent=4)
Harald Welte33f8da82021-11-03 12:14:50 +0100134
135
Harald Weltec781ab82021-05-23 11:50:19 +0200136def main(argv):
137 parser = argparse.ArgumentParser()
138 parser.add_argument("-H", "--host", help="Host/IP to bind HTTP to", default="localhost")
139 parser.add_argument("-p", "--port", help="TCP port to bind HTTP to", default=8000)
140 #parser.add_argument("-v", "--verbose", help="increase output verbosity", action='count', default=0)
141
142 args = parser.parse_args()
143
Harald Welte3f3b45a2022-07-23 13:44:20 +0200144 srr = SimRestServer()
145 srr.app.run(args.host, args.port)
Harald Weltec781ab82021-05-23 11:50:19 +0200146
147if __name__ == "__main__":
148 main(sys.argv)