Skip to content

Commit

Permalink
Transaction log, cleaned up references to certificates, etc.
Browse files Browse the repository at this point in the history
  • Loading branch information
dschuetz committed Jan 19, 2012
1 parent 53e83f4 commit da40745
Showing 1 changed file with 112 additions and 53 deletions.
165 changes: 112 additions & 53 deletions Tools/server/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,39 @@
from plistlib import *
from APNSWrapper import *
from creds import *
from datetime import datetime

#
# Simple, basic, bare-bones example test server
# Implements Apple's Mobile Device Management (MDM) protocol
# Compatible with iOS 4.x devices
# Not yet tested with iOS 5.0
#
#
# David Schuetz, Senior Consultant, Intrepidus Group
#
# Copyright 2011, Intrepidus Group
# http://intrepidusgroup.com

#
# Revision History:
#
# * August 2011 - initial release, Black Hat USA
# * January 2012 - minor tweaks, including favicon, useful README, and
# scripts to create certs, log file, etc.
#

LOGFILE = 'xactn.log'

###########################################################################
# Update this to match the UUID in the test provisioning profiles, in order
# to demonstrate removal of the profile

my_test_provisioning_uuid = 'REPLACE-ME-WITH-REAL-UUIDSTRING'


from web.wsgiserver import CherryPyWSGIServer
CherryPyWSGIServer.ssl_certificate = "Server.crt"
CherryPyWSGIServer.ssl_private_key = "Server.key"

###########################################################################

last_result = ''
Expand All @@ -37,21 +50,23 @@
'/ServerURL', 'do_mdm',
'/CheckInURL', 'do_mdm',
'/enroll', 'enroll_profile',
'/ca', 'mdm_ca',
'/favicon.ico', 'favicon',
)



def setup_commands():
global my_test_provisioning_uuid

ret_list = dict()

for cmd in ['DeviceLock', 'ProfileList', 'Restrictions',
'CertificateList', 'InstalledApplicationList',
'ProvisioningProfileList']:
'ProvisioningProfileList',
]:
ret_list[cmd] = dict( Command = dict( RequestType = cmd ))


ret_list['SecurityInfo'] = dict(
Command = dict(
RequestType = 'SecurityInfo',
Expand All @@ -72,7 +87,13 @@ def setup_commands():
'DeviceCapacity', 'DeviceName', 'ICCID', 'IMEI', 'IsRoaming',
'Model', 'ModelName', 'ModemFirmwareVersion', 'OSVersion',
'PhoneNumber', 'Product', 'ProductName', 'SIMCarrierNetwork',
'SIMMCC', 'SIMMNC', 'SerialNumber', 'UDID', 'WiFiMAC', 'UDID'
'SIMMCC', 'SIMMNC', 'SerialNumber', 'UDID', 'WiFiMAC', 'UDID',
'UnlockToken',

'MEID', 'CellularTechnology', 'BatteryLevel',
'SubscriberCarrierNetwork', 'VoiceRoamingEnabled',
'SubscriberMCC', 'SubscriberMNC', 'DataRoaming', 'VoiceRomaing',
'JailbreakDetected'
]
)
)
Expand All @@ -87,57 +108,58 @@ def setup_commands():
# commented out, and command string changed, to avoid accidentally
# erasing test devices.
#
# ret_list['DONT_EraseDevice'] = dict(
# ret_list['EraseDevice'] = dict(
# Command = dict(
# RequestType = 'DONT_EraseDevice',
# )
# )
#
if 'test.mobileconfig' in os.listdir('.'):
my_test_cfg_profile = open('test.mobileconfig', 'rb').read()
if 'Example.mobileconfig' in os.listdir('.'):
my_test_cfg_profile = open('Example.mobileconfig', 'rb').read()
pl = readPlistFromString(my_test_cfg_profile)

ret_list['InstallProfile'] = dict(
Command = dict(
RequestType = 'InstallProfile',
Payload = Data(my_test_cfg_profile)
)
)

ret_list['RemoveProfile'] = dict(
Command = dict(
RequestType = 'RemoveProfile',
Identifier = pl['PayloadIdentifier']
)
)
else:
print "Can't find test.mobileconfig in current directory."
sys.exit()

ret_list['InstallProfile'] = dict(
Command = dict(
RequestType = 'InstallProfile',
Payload = Data(my_test_cfg_profile)
)
)

else:
print "Can't find Example.mobileconfig in current directory."


if 'test.mobileprovision' in os.listdir('.'):
my_test_prov_profile = open('test.mobileprovision', 'rb').read()
else:
print "Can't find test.mobileprovision in current directory."
sys.exit()
if 'MyApp.mobileprovision' in os.listdir('.'):
my_test_prov_profile = open('MyApp.mobileprovision', 'rb').read()

ret_list['InstallProvisioningProfile'] = dict(
Command = dict(
RequestType = 'InstallProvisioningProfile',
ProvisioningProfile = Data(my_test_prov_profile)
ret_list['InstallProvisioningProfile'] = dict(
Command = dict(
RequestType = 'InstallProvisioningProfile',
ProvisioningProfile = Data(my_test_prov_profile)
)
)
)

ret_list['RemoveProvisioningProfile'] = dict(
Command = dict(
RequestType = 'RemoveProvisioningProfile',
# need an ASN.1 parser to snarf the UUID out of the signed profile
UUID = my_test_provisioning_uuid
ret_list['RemoveProvisioningProfile'] = dict(
Command = dict(
RequestType = 'RemoveProvisioningProfile',
# need an ASN.1 parser to snarf the UUID out of the signed profile
UUID = my_test_provisioning_uuid
)
)
)

else:
print "Can't find MyApp.mobileprovision in current directory."

return ret_list


class root:
def GET(self):
return home_page()
Expand All @@ -154,12 +176,11 @@ def GET(self):
current_command = cmd_data
last_sent = pprint.pformat(current_command)

wrapper = APNSNotificationWrapper('PlainCert.pem', False)
wrapper = APNSNotificationWrapper('PushCert.pem', False)
message = APNSNotification()
message.token(my_DeviceToken)
message.appendProperty(APNSProperty('mdm', my_PushMagic))
wrapper.append(message)
print "Sending push notification"
wrapper.notify()

return home_page()
Expand All @@ -170,37 +191,43 @@ class do_mdm:
global last_result
def PUT(self):
global current_command, last_result
RED=''
GREY=''
HIGH=''
LOW=''
NORMAL=''

i = web.data()
pl = readPlistFromString(i)
# print i

print "%sReceived %4d bytes: %s" % (RED, len(web.data()), GREY),
print "%sReceived %4d bytes: %s" % (HIGH, len(web.data()), NORMAL),

if pl.get('Status') == 'Idle':
print RED + "Idle Status" + GREY
print HIGH + "Idle Status" + NORMAL
rd = current_command
print "%sSent: %s%s" % (RED, rd['Command']['RequestType'], GREY)
print "%sSent: %s%s" % (HIGH, rd['Command']['RequestType'], NORMAL)
# print HIGH, rd, NORMAL

elif pl.get('MessageType') == 'TokenUpdate':
print RED+"Token Update"+GREY
print HIGH+"Token Update"+NORMAL
rd = do_TokenUpdate(pl)
print RED+"Device Enrolled!"+GREY
print HIGH+"Device Enrolled!"+NORMAL

elif pl.get('Status') == 'Acknowledged':
print RED+"Acknowledged"+GREY
print HIGH+"Acknowledged"+NORMAL
rd = dict()

else:
rd = dict()
if pl.get('MessageType') == 'Authenticate':
print RED+"Authenticate"+GREY
print HIGH+"Authenticate"+NORMAL
else:
print RED+"(other)"+GREY
print HIGH+"(other)"+NORMAL
print HIGH, pl, NORMAL
log_data(pl)
log_data(rd)

out = writePlistToString(rd)
# print LOW, out, NORMAL

q = pl.get('QueryResponses')
if q:
Expand All @@ -221,7 +248,7 @@ def home_page():
global mdm_commands, last_result, last_sent, current_command

drop_list = ''
for key in mdm_commands:
for key in sorted(mdm_commands.iterkeys()):
if current_command['Command']['RequestType'] == key:
selected = 'selected'
else:
Expand All @@ -230,13 +257,17 @@ def home_page():

out = """
<html><head><title>MDM Test Console</title></head><body>
<table border='0' width='100%%'><tr><td>
<form method="GET" action="/queue">
<select name="command">
<option value=''>Select command</option>
%s
</select>
<input type=submit value="Send"/>
</form>
</form></td>
<td align="center">Tap <a href='/enroll'>here</a> to <br/>enroll in MDM</td>
<td align="right">Tap <a href='/ca'>here</a> to install the <br/> CA Cert (for Server/Identity)</td>
</tr></table>
<hr/>
<b>Last command sent</b>
<pre>%s</pre>
Expand Down Expand Up @@ -284,10 +315,32 @@ def do_TokenUpdate(pl):
class enroll_profile:
def GET(self):

if 'enroll.mobileconfig' in os.listdir('.'):
if 'Enroll.mobileconfig' in os.listdir('.'):
web.header('Content-Type', 'application/x-apple-aspen-config;charset=utf-8')
web.header('Content-Disposition', 'attachment;filename="enroll.mobileconfig"')
return open('enroll.mobileconfig', "rb").read()
web.header('Content-Disposition', 'attachment;filename="Enroll.mobileconfig"')
return open('Enroll.mobileconfig', "rb").read()
else:
raise web.notfound()


class mdm_ca:
def GET(self):

if 'CA.crt' in os.listdir('.'):
web.header('Content-Type', 'application/octet-stream;charset=utf-8')
web.header('Content-Disposition', 'attachment;filename="CA.crt"')
return open('CA.crt', "rb").read()
else:
raise web.notfound()


class favicon:
def GET(self):

if 'favicon.ico' in os.listdir('.'):
web.header('Content-Type', 'image/x-icon;charset=utf-8')
# web.header('Content-Disposition', 'attachment;filename="favicon.ico"')
return open('favicon.ico', "rb").read()
else:
raise web.notfound()

Expand All @@ -297,7 +350,13 @@ def GET(self):
current_command = mdm_commands['DeviceLock']


def log_data(out):
fd = open(LOGFILE, "a")
fd.write(datetime.now().ctime())
fd.write(" %s\n" % repr(out))
fd.close()

if __name__ == "__main__":
print "[1;31mStarting Server[0;37m"
print "Starting Server"
app = web.application(urls, globals())
app.run()

0 comments on commit da40745

Please sign in to comment.