blob: a31aeed3464f3ad4c25c367d581d3e25492755c0 [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
24from klein import run, route
25
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 Welte6f8cf9b2022-07-06 16:28:56 +020053def set_headers(request):
54 request.setHeader('Content-Type', 'application/json')
Harald Welte33f8da82021-11-03 12:14:50 +010055
Harald Weltec781ab82021-05-23 11:50:19 +020056@route('/sim-auth-api/v1/slot/<int:slot>')
57def auth(request, slot):
58 """REST API endpoint for performing authentication against a USIM.
59 Expects a JSON body containing RAND and AUTN.
60 Returns a JSON body containing RES, CK, IK and Kc."""
61 try:
62 # there are two hex-string JSON parameters in the body: rand and autn
63 content = json.loads(request.content.read())
64 rand = content['rand']
65 autn = content['autn']
66 except:
67 request.setResponseCode(400)
68 return "Malformed Request"
69
70 try:
Harald Welte33f8da82021-11-03 12:14:50 +010071 tp, scc, card = connect_to_card(slot)
Harald Weltec781ab82021-05-23 11:50:19 +020072 except ReaderError:
73 request.setResponseCode(404)
74 return "Specified SIM Slot doesn't exist"
75 except ProtocolError:
76 request.setResponseCode(500)
77 return "Error"
78 except NoCardError:
79 request.setResponseCode(410)
80 return "No SIM card inserted in slot"
81
Harald Weltec781ab82021-05-23 11:50:19 +020082 try:
Harald Weltec781ab82021-05-23 11:50:19 +020083 card.select_adf_by_aid(adf='usim')
84 res, sw = scc.authenticate(rand, autn)
85 except SwMatchError as e:
86 request.setResponseCode(500)
87 return "Communication Error %s" % e
88
89 tp.disconnect()
90
Harald Welte6f8cf9b2022-07-06 16:28:56 +020091 set_headers(request)
Harald Weltec781ab82021-05-23 11:50:19 +020092 return json.dumps(res, indent=4)
93
Harald Welte33f8da82021-11-03 12:14:50 +010094@route('/sim-info-api/v1/slot/<int:slot>')
95def info(request, slot):
96 """REST API endpoint for obtaining information about an USIM.
97 Expects empty body in request.
98 Returns a JSON body containing ICCID, IMSI."""
99
100 try:
101 tp, scc, card = connect_to_card(slot)
102 except ReaderError:
103 request.setResponseCode(404)
104 return "Specified SIM Slot doesn't exist"
105 except ProtocolError:
106 request.setResponseCode(500)
107 return "Error"
108 except NoCardError:
109 request.setResponseCode(410)
110 return "No SIM card inserted in slot"
111
112 try:
113 card.select_adf_by_aid(adf='usim')
114 iccid, sw = card.read_iccid()
115 imsi, sw = card.read_imsi()
116 res = {"imsi": imsi, "iccid": iccid }
117 except SwMatchError as e:
118 request.setResponseCode(500)
119 return "Communication Error %s" % e
120
121 tp.disconnect()
122
Harald Welte6f8cf9b2022-07-06 16:28:56 +0200123 set_headers(request)
Harald Welte33f8da82021-11-03 12:14:50 +0100124 return json.dumps(res, indent=4)
125
126
Harald Weltec781ab82021-05-23 11:50:19 +0200127def main(argv):
128 parser = argparse.ArgumentParser()
129 parser.add_argument("-H", "--host", help="Host/IP to bind HTTP to", default="localhost")
130 parser.add_argument("-p", "--port", help="TCP port to bind HTTP to", default=8000)
131 #parser.add_argument("-v", "--verbose", help="increase output verbosity", action='count', default=0)
132
133 args = parser.parse_args()
134
135 run(args.host, args.port)
136
137if __name__ == "__main__":
138 main(sys.argv)