Skip to content

Commit

Permalink
Reworked FairPlayAES object and its init.
Browse files Browse the repository at this point in the history
  • Loading branch information
systemcrash committed Jan 8, 2022
1 parent ae8ffe2 commit 664aab8
Show file tree
Hide file tree
Showing 2 changed files with 49 additions and 97 deletions.
22 changes: 14 additions & 8 deletions ap2-receiver.py
Original file line number Diff line number Diff line change
Expand Up @@ -497,7 +497,7 @@ def __init__(self, sdp=''):


class AP2Handler(http.server.BaseHTTPRequestHandler):
aeskeys = None
aeskeyobj = None
pp = pprint.PrettyPrinter()
ntp_port, ptp_port = 0, 0
ntp_proc, ptp_proc = None, None
Expand Down Expand Up @@ -622,11 +622,10 @@ def do_ANNOUNCE(self):
self.send_response(404)
self.server.hap = None
else:
if sdp.has_fp:
# SCR_LOG.debug('Got FP AES Key from SDP')
self.aeskeys = FairPlayAES(fpaeskey=sdp.aeskey, aesiv=sdp.aesiv)
if sdp.has_fp and self.fairplay_keymsg:
self.aeskeyobj = FairPlayAES(fpaeskeyb64=sdp.aeskey, aesivb64=sdp.aesiv, keymsg=self.fairplay_keymsg)
elif sdp.has_rsa:
self.aeskeys = FairPlayAES(rsaaeskey=sdp.aeskey, aesiv=sdp.aesiv)
self.aeskeyobj = FairPlayAES(rsaaeskeyb64=sdp.aeskey, aesivb64=sdp.aesiv)
self.send_response(200)
self.send_header("Server", self.version_string())
self.send_header("CSeq", self.headers["CSeq"])
Expand Down Expand Up @@ -673,8 +672,8 @@ def do_SETUP(self):
'latencyMin': int(self.sdp.minlatency),
'latencyMax': int(self.sdp.maxlatency),
'ct': 0, # Compression Type(?)
'shk': self.aeskeys.aeskey,
'shiv': self.aeskeys.aesiv,
'shk': self.aeskeyobj.aeskey,
'shiv': self.aeskeyobj.aesiv,
'spf': int(self.sdp.spf), # sample frames per pkt
'type': int(self.sdp.payload_type),
'controlPort': 0,
Expand Down Expand Up @@ -722,6 +721,11 @@ def do_SETUP(self):

plist = readPlistFromString(body)
SCR_LOG.debug(self.pp.pformat(plist))
if 'eiv' in plist and 'ekey' in plist:
self.aesiv = plist['eiv']
self.aeskey = plist['ekey']
self.aeskeyobj = FairPlayAES(fpaeskey=self.aeskey, aesiv=self.aesiv, keymsg=self.fairplay_keymsg)

if "streams" not in plist:
SCR_LOG.debug("Sending EVENT:")
event_port, self.event_proc = EventGeneric.spawn(
Expand Down Expand Up @@ -1022,7 +1026,9 @@ def handle_X_setup(self, op: str = ''):
response = b''
content_len = int(self.headers["Content-Length"])
if content_len > 0:
body = self.rfile.read(content_len)
# This is the session fairplay_keymsg (168 bytes long)
self.fairplay_keymsg = body = self.rfile.read(content_len)

if op == 'fp':
pf = PlayFair()
pf_info = PlayFair.fairplay_s()
Expand Down
124 changes: 35 additions & 89 deletions ap2/playfair.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,108 +34,54 @@


class FairPlayAES():
def __init__(self, fpaeskey=None, rsaaeskey=None, aesiv=None):
def __init__(self,
rsaaeskeyb64=None, # either RSA
fpaeskeyb64=None, aesivb64=None, # or b64 encoded key+iv
fpaeskey=None, aesiv=None, # or binary key+iv
keymsg=None, # Needed to decrypt the FP AES keys
):
self.logger = get_screen_logger(__name__, 'DEBUG')
if rsaaeskey:
if rsaaeskeyb64:
airportkey = RSA.importKey(AIRPORT_PRIVATE_KEY)
cipher = PKCS1_OAEP.new(airportkey)

binkey = base64.standard_b64decode(rsaaeskey + '==')
binkey = self.decodeb64(rsaaeskeyb64)
"""
Decoded RSA keys are 256 bytes
"""
self.aeskey = cipher.decrypt(binkey)
if len(self.aeskey) == 16:
self.logger.info('Got RSA AES key')
"""
Decoded keys look like (length 256 bytes):
'\xbf\x92\xc0k-N\xb5\xdf\xbfwM\xda\xc0\xb0\xf1K\xe8\xab4\x83\xd4:V'
'\xc6dS#\xe3\xce\xd3?E\xe2x\xc5\x1e\x9e\xd0\xc6\x028\x90\xa76\x1f~'
'\xa7j\xbcuH\x16\xbe\xb9\x1c\xd7\xb7\xd5X\x8b\x81\x9d\xa0\x82\xd4\'
'\\x1a\x81\xf5\xa0R\xc2|H\xc4\xca\x1d\xef\xd0\x1b\xd6&\xc3\xb9P`i'
'\xa6r\x97\xd2\x0e^\xa7\xa8\x9aHa\x06\x91\x04J(\x08\xa4P\xf9C\x7f'
'\x15\xee\xa8|\x1b\xcb\xc9\xd1\xc7\xa1\xcc\x95\xef-+;\xbb\x8e$\xcax'
'\x8a\xeb\xbf;\xdf\xc8\xa8)\xe6\x17jp\x85O7i\xd4A=\x9a\xaeEb\x92\x9f'
'\x95\xce\xb3\xf6\x82\xb8d\x1d\xe1o;\xce\x81\x90\xe6lC\xa7\x0b\xd4'
'\xc6@\x8dN\xe9"\xf5.p\xd8\xde\x97`~~\xd3\xe8=\xa1\x88\\\x04\xfb\x0c'
'\xd9Y\xb5\x0b\x05\xdd\x8dz2M\x1e\xa90\xfbQ6$\xa1\xf7\x05\x01[\xa0^'
'\x1e\xf2G\xf2$\x8a$&\xaa\xc5\xaf\xd8\xa9p\xbb\x9b\x95\x9b\'\xf4@.o7'
'\x91\x1c\xbb\x1a\xbb\xec\x1a3\x96'
AES keys obtained are 16 bytes
"""

else:
self.logger.info('Got FP AES key: Cannot yet decrypt.') # , fpaeskey)
self.aeskey = base64.standard_b64decode(fpaeskey)
# TODO: Now decode/decrypt the AES key...
if len(self.aeskey) == 16:
self.logger.info('Got RSA AES key (base64)')
elif fpaeskeyb64:
self.logger.info('Got FP AES key (base64)')
self.aeskey = self.decodeb64(fpaeskeyb64)
"""
Decoded keys look like (length 72 bytes):
'FPLY\x01\x02\x01\x00\x00\x00\x00<\x00\x00\x00\x00\xe4\x90V\xc8\xf2%'
'\xebP:k\xe3\xd41\xe8\xa7{\x00\x00\x00\x10\xbfs\xc8\xb0\x9c\x9b7\xe8Fb#'
'\xbfN\xa6\xa7\xa5I\x9cW\xe6\x0b\xf6GC\x8f\xd2\xbb\x7f@3s\xef\x06i2\x7f'
Decoded AES keys are 72 bytes long starting:
'FPLY...'
Note: they are not yet decrypted (MFi)
"""
# Just to keep the Audio module happy later with a 32 byte key size.
self.aeskey = self.aeskey[16:48]
self.aesiv = base64.standard_b64decode(aesiv + '==')
if len(self.aesiv) == 16:
elif fpaeskey:
# Just to keep the Audio module happy later with a 32 byte key size.
self.aeskey = fpaeskey[16:48]
self.logger.info('Got FP AES key')

# Handle AES IV
if aesivb64:
self.aesiv = self.decodeb64(aesivb64)
self.logger.info('Got AES IV (base64)')
elif aesiv:
self.aesiv = aesiv
self.logger.info('Got AES IV')

# @property
def aesiv(self):
return self.aesiv

# @aesiv.setter
# def aesiv(self, _val):
# self.aesiv = _val

# @property
def aeskey(self):
return self.aeskey

# @aeskey.setter
# def aeskey(self, _val):
# self.aeskey = _val


""" FOR FAIRPLAY
if (fpaeskeystr) {
unsigned char fpaeskey[72];
int fpaeskeylen;
fpaeskeylen = rsakey_decode(conn->raop->rsakey, fpaeskey, sizeof(fpaeskey), fpaeskeystr);
if (fpaeskeylen > 0) {
fairplay_decrypt(conn->fairplay, fpaeskey, aeskey);
aeskeylen = sizeof(aeskey);
}
}
input key is 72 bytes
output msg is 16 bytes
void generate_key_schedule(unsigned char* key_material, uint32_t key_schedule[11][4]);
void generate_session_key(unsigned char* oldSap, unsigned char* messageIn, unsigned char* sessionKey);
void cycle(unsigned char* block, uint32_t key_schedule[11][4]);
void z_xor(unsigned char* in, unsigned char* out, int blocks);
void x_xor(unsigned char* in, unsigned char* out, int blocks);
extern unsigned char default_sap[];
void playfair_decrypt(unsigned char* message3, unsigned char* cipherText, unsigned char* keyOut)
{
unsigned char* chunk1 = &cipherText[16];
unsigned char* chunk2 = &cipherText[56];
int i;
unsigned char blockIn[16];
unsigned char sapKey[16];
uint32_t key_schedule[11][4];
generate_session_key(default_sap, message3, sapKey);
generate_key_schedule(sapKey, key_schedule);
z_xor(chunk2, blockIn, 1);
cycle(blockIn, key_schedule);
for (i = 0; i < 16; i++) {
keyOut[i] = blockIn[i] ^ chunk1[i];
}
x_xor(keyOut, keyOut, 1);
z_xor(keyOut, keyOut, 1);
}
"""
def decodeb64(self, _input):
return base64.standard_b64decode(_input + '==')

# ===========


class PlayFair:
Expand Down

0 comments on commit 664aab8

Please sign in to comment.