Closed fduncanh closed 2 years ago
This is an excellent find! Thanks for your efforts! I'm sorry I'm so slow with my responses at the moment, I'm just very busy with other stuff. If you submit a PR with just this fix (or a rework to make our crypto code follow the OpenSSL EVP guidelines), I'll gladly accept it :)
I have a potential fix for this that involves using Final. I believe the issue is a padding setting, but I haven't tested with OpenSSL 3.0 yet & I'm a little afraid to install it on Raspberry Pi OS.
Please check out the commit I just pushed, b9e096354fce474d5c9ba1e4a04a85bb9f8cdb03
It sounds like that commit fixes this issue. I want to refactor the lib/crypto stuff into the actual callers & replace the aes_init
/aes_destroy
calls with calls to EVP_DecryptInit_ex
once and EVP_CIPHER_CTX_reset
to prevent a much of malloc
/free
in this tight loop.
Edit: The bug is in crypto.c. (EVP_DecriptFinal is usually called after EVP_DecriptUpdate to finalize the unencrypted remainder ( < 16 bytes) of the AES-CBC encrypted packet. But this is is already being done in raop_buffer.c: so it is not done in this code.
A simple workaround that seems to stop the last 16 encrypted bytes from being lost is the replacement where
line 178 lib/raop_buffer.c: aes_cbc_decrypt(aes_ctx_audio, &data[12], output, encryptedlen);
is modified to
aes_cbc_decrypt(aes_ctx_audio, &data[12], output, encryptedlen + 15);
Without this, EVP_DecriptUpdate seems to leave the last part of the encrypted packet for decryption by a call to EVP_DecriptFinal, but this is not done in crypto.c. Adding 15 to the packet length reported to EVP_DecriptUpdate fools it into decrypting all the encrypted bytes: the unencrypted <16 byte remainder is then handled "manually" in raop_buffer.c . The "correct" solution would be to rework crypto.c so it follows the OpenSSL EVP guidelines, but the simple fix seems to do job without that. (sidecomment: the author of the OpenSSL EVP utilities had a spelling issue with "cript" as opposed to "crypt"!).
Since encryptedlen %16 = 0, there is no danger of a buffer overrun by aes_cbc_decrypt, which eventually calls EVP_DecriptUpdate from OpenSSL.
I would post a pull request but they are being ignored. It's a simple fix. I'll add it to PR #266, but this is being ignored. This fix will be used to add ALAC support to the fork UxPlay 1.3x https://github.com/FDH2/UxPlay (which only uses GStreamer rendering)
Edit 2:
Fixing this bug has made it easy to add support for ALAC streams, it was this bug that prevented adding ALAC support before. Fixing it also seems to improve aac (MPEG4) audio (although the lost 16 bytes didnt seem to have much effect on AAC streams).
The missing bytes were fatal for ALAC, as the start of the Airplay stream seems to involve an initial send of a 32 byte header, and the bug meant that the second (last) set of 16 bytes were lost.
------------------------------------------------end of edit --------------------------------------------------
I've been trying to get ALAC streams to play in rpiplay (and uxplay), with gstreamer. Comparing the audio stream from shairplay (parent of raop in rpiplay) with that from rpiplay, I found that the last 16 entrypted bytes are each set to 00 (before any residual less than 16 unencrypted bytes are appended to the packet) The same is true for the AAC-ELD mpeg v4 stream that does play in current rpiplay.
This is very surprising: is this a feature of an AAC-ELD packet? (if this was a corruption of the AAC packet I wouldn't expect it to play, but perhaps AAC is resilent against losing those 16 bytes).
example of a decrypted (not yet decoded) AAC packet
example of a decrypted (not yet decoded) ALAC packet :
The rest of the ALAC packet is good. This is not an inserted extra 16 bytes of 00, it is a lost 16 bytes of the decrypted packet. The last 16 encrypted bytes are NOT 00 before the AES CTR decryption is applied.
The relevant code from raop_buffer.c :+1: