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