mirror of
https://gitea.osmocom.org/sim-card/pysim.git
synced 2026-03-16 18:38:32 +03:00
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
This commit is contained in:
155
contrib/sim-rest-client.py
Executable file
155
contrib/sim-rest-client.py
Executable file
@@ -0,0 +1,155 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# sim-rest-client.py: client program to test the sim-rest-server.py
|
||||
#
|
||||
# this will generate authentication tuples just like a HLR / HSS
|
||||
# and will then send the related challenge to the REST interface
|
||||
# of sim-rest-server.py
|
||||
#
|
||||
# sim-rest-server.py will then contact the SIM card to perform the
|
||||
# authentication (just like a 3GPP RAN), and return the results via
|
||||
# the REST to sim-rest-client.py.
|
||||
#
|
||||
# (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/>.
|
||||
|
||||
from typing import Optional, Dict
|
||||
|
||||
import sys
|
||||
import argparse
|
||||
import secrets
|
||||
import requests
|
||||
|
||||
from CryptoMobile.Milenage import Milenage
|
||||
from CryptoMobile.utils import xor_buf
|
||||
|
||||
def unpack48(x:bytes) -> int:
|
||||
"""Decode a big-endian 48bit number from binary to integer."""
|
||||
return int.from_bytes(x, byteorder='big')
|
||||
|
||||
def pack48(x:int) -> bytes:
|
||||
"""Encode a big-endian 48bit number from integer to binary."""
|
||||
return x.to_bytes(48 // 8, byteorder='big')
|
||||
|
||||
def milenage_generate(opc:bytes, amf:bytes, k:bytes, sqn:bytes, rand:bytes) -> Dict[str, bytes]:
|
||||
"""Generate an MILENAGE Authentication Tuple."""
|
||||
m = Milenage(None)
|
||||
m.set_opc(opc)
|
||||
mac_a = m.f1(k, rand, sqn, amf)
|
||||
res, ck, ik, ak = m.f2345(k, rand)
|
||||
|
||||
# AUTN = (SQN ^ AK) || AMF || MAC
|
||||
sqn_ak = xor_buf(sqn, ak)
|
||||
autn = b''.join([sqn_ak, amf, mac_a])
|
||||
|
||||
return {'res': res, 'ck': ck, 'ik': ik, 'autn': autn}
|
||||
|
||||
def milenage_auts(opc:bytes, k:bytes, rand:bytes, auts:bytes) -> Optional[bytes]:
|
||||
"""Validate AUTS. If successful, returns SQN_MS"""
|
||||
amf = b'\x00\x00' # TS 33.102 Section 6.3.3
|
||||
m = Milenage(None)
|
||||
m.set_opc(opc)
|
||||
ak = m.f5star(k, rand)
|
||||
|
||||
sqn_ak = auts[:6]
|
||||
sqn = xor_buf(sqn_ak, ak[:6])
|
||||
|
||||
mac_s = m.f1star(k, rand, sqn, amf)
|
||||
if mac_s == auts[6:14]:
|
||||
return sqn
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def build_url(suffix:str) -> str:
|
||||
"""Build an URL from global server_host, server_port, BASE_PATH and suffix."""
|
||||
BASE_PATH= "/sim-auth-api/v1"
|
||||
return "http://%s:%u%s%s" % (server_host, server_port, BASE_PATH, suffix)
|
||||
|
||||
|
||||
def rest_post(suffix:str, js:Optional[dict] = None):
|
||||
"""Perform a RESTful POST."""
|
||||
url = build_url(suffix)
|
||||
if verbose:
|
||||
print("POST %s (%s)" % (url, str(js)))
|
||||
resp = requests.post(url, json=js)
|
||||
if verbose:
|
||||
print("-> %s" % (resp))
|
||||
if not resp.ok:
|
||||
print("POST failed")
|
||||
return resp
|
||||
|
||||
|
||||
|
||||
def main(argv):
|
||||
global server_port, server_host, verbose
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("-H", "--host", help="Host to connect to", default="localhost")
|
||||
parser.add_argument("-p", "--port", help="TCP port to connect to", default=8000)
|
||||
parser.add_argument("-v", "--verbose", help="increase output verbosity", action='count', default=0)
|
||||
parser.add_argument("-n", "--slot-nr", help="SIM slot number", type=int, default=0)
|
||||
parser.add_argument("-c", "--count", help="Auth count", type=int, default=10)
|
||||
|
||||
parser.add_argument("-k", "--key", help="Secret key K (hex)", type=str, required=True)
|
||||
parser.add_argument("-o", "--opc", help="Secret OPc (hex)", type=str, required=True)
|
||||
parser.add_argument("-a", "--amf", help="AMF Field (hex)", type=str, default="0000")
|
||||
parser.add_argument("-s", "--sqn", help="SQN Field (hex)", type=str, default="000000000000")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
server_host = args.host
|
||||
server_port = args.port
|
||||
verbose = args.verbose
|
||||
|
||||
#opc = bytes.fromhex('767A662ACF4587EB0C450C6A95540A04')
|
||||
#k = bytes.fromhex('876B2D8D403EE96755BEF3E0A1857EBE')
|
||||
opc = bytes.fromhex(args.opc)
|
||||
k = bytes.fromhex(args.key)
|
||||
amf = bytes.fromhex(args.amf)
|
||||
sqn = bytes.fromhex(args.sqn)
|
||||
|
||||
for i in range(args.count):
|
||||
rand = secrets.token_bytes(16)
|
||||
t = milenage_generate(opc=opc, amf=amf, k=k, sqn=sqn, rand=rand)
|
||||
|
||||
req_json = {'rand': rand.hex(), 'autn': t['autn'].hex()}
|
||||
print("-> %s" % req_json)
|
||||
resp = rest_post('/slot/%u' % args.slot_nr, req_json)
|
||||
resp_json = resp.json()
|
||||
print("<- %s" % resp_json)
|
||||
if 'synchronisation_failure' in resp_json:
|
||||
auts = bytes.fromhex(resp_json['synchronisation_failure']['auts'])
|
||||
sqn_ms = milenage_auts(opc, k, rand, auts)
|
||||
if sqn_ms is not False:
|
||||
print("SQN_MS = %s" % sqn_ms.hex())
|
||||
sqn_ms_int = unpack48(sqn_ms)
|
||||
# we assume an IND bit-length of 5 here
|
||||
sqn = pack48(sqn_ms_int + (1 << 5))
|
||||
else:
|
||||
raise RuntimeError("AUTS auth failure during re-sync?!?")
|
||||
elif 'successful_3g_authentication' in resp_json:
|
||||
auth_res = resp_json['successful_3g_authentication']
|
||||
assert bytes.fromhex(auth_res['res']) == t['res']
|
||||
assert bytes.fromhex(auth_res['ck']) == t['ck']
|
||||
assert bytes.fromhex(auth_res['ik']) == t['ik']
|
||||
# we assume an IND bit-length of 5 here
|
||||
sqn = pack48(unpack48(sqn) + (1 << 5))
|
||||
else:
|
||||
raise RuntimeError("Auth failure")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main(sys.argv)
|
||||
Reference in New Issue
Block a user