Skip to content

Commit 664aab8

Browse files
committed
Reworked FairPlayAES object and its init.
1 parent ae8ffe2 commit 664aab8

File tree

2 files changed

+49
-97
lines changed

2 files changed

+49
-97
lines changed

ap2-receiver.py

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -497,7 +497,7 @@ def __init__(self, sdp=''):
497497

498498

499499
class AP2Handler(http.server.BaseHTTPRequestHandler):
500-
aeskeys = None
500+
aeskeyobj = None
501501
pp = pprint.PrettyPrinter()
502502
ntp_port, ptp_port = 0, 0
503503
ntp_proc, ptp_proc = None, None
@@ -622,11 +622,10 @@ def do_ANNOUNCE(self):
622622
self.send_response(404)
623623
self.server.hap = None
624624
else:
625-
if sdp.has_fp:
626-
# SCR_LOG.debug('Got FP AES Key from SDP')
627-
self.aeskeys = FairPlayAES(fpaeskey=sdp.aeskey, aesiv=sdp.aesiv)
625+
if sdp.has_fp and self.fairplay_keymsg:
626+
self.aeskeyobj = FairPlayAES(fpaeskeyb64=sdp.aeskey, aesivb64=sdp.aesiv, keymsg=self.fairplay_keymsg)
628627
elif sdp.has_rsa:
629-
self.aeskeys = FairPlayAES(rsaaeskey=sdp.aeskey, aesiv=sdp.aesiv)
628+
self.aeskeyobj = FairPlayAES(rsaaeskeyb64=sdp.aeskey, aesivb64=sdp.aesiv)
630629
self.send_response(200)
631630
self.send_header("Server", self.version_string())
632631
self.send_header("CSeq", self.headers["CSeq"])
@@ -673,8 +672,8 @@ def do_SETUP(self):
673672
'latencyMin': int(self.sdp.minlatency),
674673
'latencyMax': int(self.sdp.maxlatency),
675674
'ct': 0, # Compression Type(?)
676-
'shk': self.aeskeys.aeskey,
677-
'shiv': self.aeskeys.aesiv,
675+
'shk': self.aeskeyobj.aeskey,
676+
'shiv': self.aeskeyobj.aesiv,
678677
'spf': int(self.sdp.spf), # sample frames per pkt
679678
'type': int(self.sdp.payload_type),
680679
'controlPort': 0,
@@ -722,6 +721,11 @@ def do_SETUP(self):
722721

723722
plist = readPlistFromString(body)
724723
SCR_LOG.debug(self.pp.pformat(plist))
724+
if 'eiv' in plist and 'ekey' in plist:
725+
self.aesiv = plist['eiv']
726+
self.aeskey = plist['ekey']
727+
self.aeskeyobj = FairPlayAES(fpaeskey=self.aeskey, aesiv=self.aesiv, keymsg=self.fairplay_keymsg)
728+
725729
if "streams" not in plist:
726730
SCR_LOG.debug("Sending EVENT:")
727731
event_port, self.event_proc = EventGeneric.spawn(
@@ -1022,7 +1026,9 @@ def handle_X_setup(self, op: str = ''):
10221026
response = b''
10231027
content_len = int(self.headers["Content-Length"])
10241028
if content_len > 0:
1025-
body = self.rfile.read(content_len)
1029+
# This is the session fairplay_keymsg (168 bytes long)
1030+
self.fairplay_keymsg = body = self.rfile.read(content_len)
1031+
10261032
if op == 'fp':
10271033
pf = PlayFair()
10281034
pf_info = PlayFair.fairplay_s()

ap2/playfair.py

Lines changed: 35 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -34,108 +34,54 @@
3434

3535

3636
class FairPlayAES():
37-
def __init__(self, fpaeskey=None, rsaaeskey=None, aesiv=None):
37+
def __init__(self,
38+
rsaaeskeyb64=None, # either RSA
39+
fpaeskeyb64=None, aesivb64=None, # or b64 encoded key+iv
40+
fpaeskey=None, aesiv=None, # or binary key+iv
41+
keymsg=None, # Needed to decrypt the FP AES keys
42+
):
3843
self.logger = get_screen_logger(__name__, 'DEBUG')
39-
if rsaaeskey:
44+
if rsaaeskeyb64:
4045
airportkey = RSA.importKey(AIRPORT_PRIVATE_KEY)
4146
cipher = PKCS1_OAEP.new(airportkey)
4247

43-
binkey = base64.standard_b64decode(rsaaeskey + '==')
48+
binkey = self.decodeb64(rsaaeskeyb64)
49+
"""
50+
Decoded RSA keys are 256 bytes
51+
"""
4452
self.aeskey = cipher.decrypt(binkey)
45-
if len(self.aeskey) == 16:
46-
self.logger.info('Got RSA AES key')
4753
"""
48-
Decoded keys look like (length 256 bytes):
49-
'\xbf\x92\xc0k-N\xb5\xdf\xbfwM\xda\xc0\xb0\xf1K\xe8\xab4\x83\xd4:V'
50-
'\xc6dS#\xe3\xce\xd3?E\xe2x\xc5\x1e\x9e\xd0\xc6\x028\x90\xa76\x1f~'
51-
'\xa7j\xbcuH\x16\xbe\xb9\x1c\xd7\xb7\xd5X\x8b\x81\x9d\xa0\x82\xd4\'
52-
'\\x1a\x81\xf5\xa0R\xc2|H\xc4\xca\x1d\xef\xd0\x1b\xd6&\xc3\xb9P`i'
53-
'\xa6r\x97\xd2\x0e^\xa7\xa8\x9aHa\x06\x91\x04J(\x08\xa4P\xf9C\x7f'
54-
'\x15\xee\xa8|\x1b\xcb\xc9\xd1\xc7\xa1\xcc\x95\xef-+;\xbb\x8e$\xcax'
55-
'\x8a\xeb\xbf;\xdf\xc8\xa8)\xe6\x17jp\x85O7i\xd4A=\x9a\xaeEb\x92\x9f'
56-
'\x95\xce\xb3\xf6\x82\xb8d\x1d\xe1o;\xce\x81\x90\xe6lC\xa7\x0b\xd4'
57-
'\xc6@\x8dN\xe9"\xf5.p\xd8\xde\x97`~~\xd3\xe8=\xa1\x88\\\x04\xfb\x0c'
58-
'\xd9Y\xb5\x0b\x05\xdd\x8dz2M\x1e\xa90\xfbQ6$\xa1\xf7\x05\x01[\xa0^'
59-
'\x1e\xf2G\xf2$\x8a$&\xaa\xc5\xaf\xd8\xa9p\xbb\x9b\x95\x9b\'\xf4@.o7'
60-
'\x91\x1c\xbb\x1a\xbb\xec\x1a3\x96'
54+
AES keys obtained are 16 bytes
6155
"""
62-
63-
else:
64-
self.logger.info('Got FP AES key: Cannot yet decrypt.') # , fpaeskey)
65-
self.aeskey = base64.standard_b64decode(fpaeskey)
66-
# TODO: Now decode/decrypt the AES key...
56+
if len(self.aeskey) == 16:
57+
self.logger.info('Got RSA AES key (base64)')
58+
elif fpaeskeyb64:
59+
self.logger.info('Got FP AES key (base64)')
60+
self.aeskey = self.decodeb64(fpaeskeyb64)
6761
"""
68-
Decoded keys look like (length 72 bytes):
69-
'FPLY\x01\x02\x01\x00\x00\x00\x00<\x00\x00\x00\x00\xe4\x90V\xc8\xf2%'
70-
'\xebP:k\xe3\xd41\xe8\xa7{\x00\x00\x00\x10\xbfs\xc8\xb0\x9c\x9b7\xe8Fb#'
71-
'\xbfN\xa6\xa7\xa5I\x9cW\xe6\x0b\xf6GC\x8f\xd2\xbb\x7f@3s\xef\x06i2\x7f'
62+
Decoded AES keys are 72 bytes long starting:
63+
'FPLY...'
64+
Note: they are not yet decrypted (MFi)
7265
"""
7366
# Just to keep the Audio module happy later with a 32 byte key size.
7467
self.aeskey = self.aeskey[16:48]
75-
self.aesiv = base64.standard_b64decode(aesiv + '==')
76-
if len(self.aesiv) == 16:
68+
elif fpaeskey:
69+
# Just to keep the Audio module happy later with a 32 byte key size.
70+
self.aeskey = fpaeskey[16:48]
71+
self.logger.info('Got FP AES key')
72+
73+
# Handle AES IV
74+
if aesivb64:
75+
self.aesiv = self.decodeb64(aesivb64)
76+
self.logger.info('Got AES IV (base64)')
77+
elif aesiv:
78+
self.aesiv = aesiv
7779
self.logger.info('Got AES IV')
7880

79-
# @property
80-
def aesiv(self):
81-
return self.aesiv
82-
83-
# @aesiv.setter
84-
# def aesiv(self, _val):
85-
# self.aesiv = _val
86-
87-
# @property
88-
def aeskey(self):
89-
return self.aeskey
90-
91-
# @aeskey.setter
92-
# def aeskey(self, _val):
93-
# self.aeskey = _val
94-
95-
96-
""" FOR FAIRPLAY
97-
if (fpaeskeystr) {
98-
unsigned char fpaeskey[72];
99-
int fpaeskeylen;
100-
101-
fpaeskeylen = rsakey_decode(conn->raop->rsakey, fpaeskey, sizeof(fpaeskey), fpaeskeystr);
102-
if (fpaeskeylen > 0) {
103-
fairplay_decrypt(conn->fairplay, fpaeskey, aeskey);
104-
aeskeylen = sizeof(aeskey);
105-
}
106-
}
107-
108-
input key is 72 bytes
109-
output msg is 16 bytes
110-
111-
void generate_key_schedule(unsigned char* key_material, uint32_t key_schedule[11][4]);
112-
void generate_session_key(unsigned char* oldSap, unsigned char* messageIn, unsigned char* sessionKey);
113-
void cycle(unsigned char* block, uint32_t key_schedule[11][4]);
114-
void z_xor(unsigned char* in, unsigned char* out, int blocks);
115-
void x_xor(unsigned char* in, unsigned char* out, int blocks);
116-
117-
extern unsigned char default_sap[];
118-
119-
void playfair_decrypt(unsigned char* message3, unsigned char* cipherText, unsigned char* keyOut)
120-
{
121-
unsigned char* chunk1 = &cipherText[16];
122-
unsigned char* chunk2 = &cipherText[56];
123-
int i;
124-
unsigned char blockIn[16];
125-
unsigned char sapKey[16];
126-
uint32_t key_schedule[11][4];
127-
generate_session_key(default_sap, message3, sapKey);
128-
generate_key_schedule(sapKey, key_schedule);
129-
z_xor(chunk2, blockIn, 1);
130-
cycle(blockIn, key_schedule);
131-
for (i = 0; i < 16; i++) {
132-
keyOut[i] = blockIn[i] ^ chunk1[i];
133-
}
134-
x_xor(keyOut, keyOut, 1);
135-
z_xor(keyOut, keyOut, 1);
136-
}
137-
138-
"""
81+
def decodeb64(self, _input):
82+
return base64.standard_b64decode(_input + '==')
83+
84+
# ===========
13985

14086

14187
class PlayFair:

0 commit comments

Comments
 (0)