pysim/contrib/sim-rest-server.py

159 lines
5.0 KiB
Python
Executable File

#!/usr/bin/env python3
# RESTful HTTP service for performing authentication against USIM cards
#
# (C) 2021-2022 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 Klein
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
def connect_to_card(slot_nr:int):
tp = PcscSimLink(slot_nr, apdu_tracer=ApduPrintTracer())
tp.connect()
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"
card.read_aids()
card.select_adf_by_aid(adf='usim')
return tp, scc, card
class ApiError:
def __init__(self, msg:str, sw=None):
self.msg = msg
self.sw = sw
def __str__(self):
d = {'error': {'message':self.msg}}
if self.sw:
d['error']['status_word'] = self.sw
return json.dumps(d)
def set_headers(request):
request.setHeader('Content-Type', 'application/json')
class SimRestServer:
app = Klein()
@app.handle_errors(NoCardError)
def no_card_error(self, request, failure):
set_headers(request)
request.setResponseCode(410)
return str(ApiError("No SIM card inserted in slot"))
@app.handle_errors(ReaderError)
def reader_error(self, request, failure):
set_headers(request)
request.setResponseCode(404)
return str(ApiError("Reader Error: Specified SIM Slot doesn't exist"))
@app.handle_errors(ProtocolError)
def protocol_error(self, request, failure):
set_headers(request)
request.setResponseCode(500)
return str(ApiError("Protocol Error: %s" % failure.value))
@app.handle_errors(SwMatchError)
def sw_match_error(self, request, failure):
set_headers(request)
request.setResponseCode(500)
sw = failure.value.sw_actual
if sw == '9862':
return str(ApiError("Card Authentication Error - Incorrect MAC", sw))
elif sw == '6982':
return str(ApiError("Security Status not satisfied - Card PIN enabled?", sw))
else:
return str(ApiError("Card Communication Error %s" % failure.value), sw)
@app.route('/sim-auth-api/v1/slot/<int:slot>')
def auth(self, 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:
set_headers(request)
request.setResponseCode(400)
return str(ApiError("Malformed Request"))
tp, scc, card = connect_to_card(slot)
card.select_adf_by_aid(adf='usim')
res, sw = scc.authenticate(rand, autn)
tp.disconnect()
set_headers(request)
return json.dumps(res, indent=4)
@app.route('/sim-info-api/v1/slot/<int:slot>')
def info(self, request, slot):
"""REST API endpoint for obtaining information about an USIM.
Expects empty body in request.
Returns a JSON body containing ICCID, IMSI."""
tp, scc, card = connect_to_card(slot)
card.select_adf_by_aid(adf='usim')
iccid, sw = card.read_iccid()
imsi, sw = card.read_imsi()
res = {"imsi": imsi, "iccid": iccid }
tp.disconnect()
set_headers(request)
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()
srr = SimRestServer()
srr.app.run(args.host, args.port)
if __name__ == "__main__":
main(sys.argv)