contrib: Add sim-rest-{server,client}.py
sim-rest-server.py can be used to provide a RESTful API to allow remote
clients to perform the authentication command against a SIM card in a
PC/SC reader.
sim-rest-client.py is an example client against sim-rest-server.py
which can be used to test the functionality of sim-rest-server.py.
Change-Id: I738ca3109ab038d4f5595cc1dab6a49087df5886
diff --git a/contrib/sim-rest-server.py b/contrib/sim-rest-server.py
new file mode 100755
index 0000000..0f77dfe
--- /dev/null
+++ b/contrib/sim-rest-server.py
@@ -0,0 +1,92 @@
+#!/usr/bin/env python3
+
+# RESTful HTTP service for performing authentication against USIM cards
+#
+# (C) 2021 by Harald Welte <laforge@osmocom.org>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import json
+import sys
+import argparse
+
+from klein import run, route
+
+from pySim.transport import ApduTracer
+from pySim.transport.pcsc import PcscSimLink
+from pySim.commands import SimCardCommands
+from pySim.cards import UsimCard
+from pySim.exceptions import *
+
+class ApduPrintTracer(ApduTracer):
+ def trace_response(self, cmd, sw, resp):
+ #print("CMD: %s -> RSP: %s %s" % (cmd, sw, resp))
+ pass
+
+@route('/sim-auth-api/v1/slot/<int:slot>')
+def auth(request, slot):
+ """REST API endpoint for performing authentication against a USIM.
+ Expects a JSON body containing RAND and AUTN.
+ Returns a JSON body containing RES, CK, IK and Kc."""
+ try:
+ # there are two hex-string JSON parameters in the body: rand and autn
+ content = json.loads(request.content.read())
+ rand = content['rand']
+ autn = content['autn']
+ except:
+ request.setResponseCode(400)
+ return "Malformed Request"
+
+ try:
+ tp = PcscSimLink(slot, apdu_tracer=ApduPrintTracer())
+ tp.connect()
+ except ReaderError:
+ request.setResponseCode(404)
+ return "Specified SIM Slot doesn't exist"
+ except ProtocolError:
+ request.setResponseCode(500)
+ return "Error"
+ except NoCardError:
+ request.setResponseCode(410)
+ return "No SIM card inserted in slot"
+
+ scc = SimCardCommands(tp)
+ card = UsimCard(scc)
+ # this should be part of UsimCard, but FairewavesSIM breaks with that :/
+ scc.cla_byte = "00"
+ scc.sel_ctrl = "0004"
+ try:
+ card.read_aids()
+ card.select_adf_by_aid(adf='usim')
+ res, sw = scc.authenticate(rand, autn)
+ except SwMatchError as e:
+ request.setResponseCode(500)
+ return "Communication Error %s" % e
+
+ tp.disconnect()
+
+ return json.dumps(res, indent=4)
+
+def main(argv):
+ parser = argparse.ArgumentParser()
+ parser.add_argument("-H", "--host", help="Host/IP to bind HTTP to", default="localhost")
+ parser.add_argument("-p", "--port", help="TCP port to bind HTTP to", default=8000)
+ #parser.add_argument("-v", "--verbose", help="increase output verbosity", action='count', default=0)
+
+ args = parser.parse_args()
+
+ run(args.host, args.port)
+
+if __name__ == "__main__":
+ main(sys.argv)