diff --git a/README.html b/README.html index 7a8e4e2..b570d00 100644 --- a/README.html +++ b/README.html @@ -1,6 +1,6 @@
This project is a GPLv3 open source unix AirPlay2 Mirror server for Linux, macOS, and *BSD. It is now hosted at the github site https://github.com/FDH2/UxPlay (where development and user-assistance now takes place), although it initially was developed by antimof using code from RPiPlay, which in turn derives from AirplayServer, shairplay, and playfair. (The antimof site is mainly inactive, but periodically posts updates pulled from the main UxPlay site).
-Its main use is to act like an AppleTV for screen-mirroring (with audio) of iOS/iPadOS/macOS clients (iPhones, iPads, MacBooks) in a window on the server display (with the possibility of sharing that window on screen-sharing applications such as Zoom) on a host running Linux, macOS, or other unix. UxPlay supports a “legacy” form of Apple’s AirPlay Mirror protocol introduced in iOS 12; client devices running iOS/iPadOS 12 or later are supported, as is a (nonfree) Windows-based AirPlay-client software emulator, AirMyPC. Older (32-bit) client devices that can only run iOS 9.3 or iOS 10.3 are currently only partially supported by UxPlay: reports indicate that screen-mirroring video works, but a so-far not-understood protocol difference means the audio stream is not correctly decrypted. (Details of what is publically known about Apple’s AirPlay2 protocol can be found here and here).
+Its main use is to act like an AppleTV for screen-mirroring (with audio) of iOS/iPadOS/macOS clients (iPhones, iPads, MacBooks) in a window on the server display (with the possibility of sharing that window on screen-sharing applications such as Zoom) on a host running Linux, macOS, or other unix. UxPlay supports a “legacy” form of Apple’s AirPlay Mirror protocol introduced in iOS 12; client devices running iOS/iPadOS 12 or later are supported, as is a (nonfree) Windows-based AirPlay-client software emulator, AirMyPC. Older (32-bit) client devices that can only run iOS 9.3 or iOS 10.3 are currently partially supported by UxPlay: reports indicate that screen-mirroring video works, audio is a work in progess. (Details of what is publically known about Apple’s AirPlay2 protocol can be found here and here).
The UxPlay server and its client must be on the same local area network, on which a Bonjour/Zeroconf mDNS/DNS-SD server is also running (only DNS-SD “Service Discovery” service is strictly necessary, it is not necessary that the local network also be of the “.local” mDNS-based type). On Linux and BSD Unix servers, this is usually provided by Avahi, through the avahi-daemon service, and is included in most Linux distributions (this service can also be provided by macOS, iOS or Windows servers).
Connections to the UxPlay server by iOS/MacOS clients can be initiated both in AirPlay Mirror mode (which streams lossily-compressed AAC audio while mirroring the client screen, or in the alternative AirPlay Audio mode which streams Apple Lossless (ALAC) audio without screen mirroring (the accompanying metadata and cover art in this mode is not displayed). Switching between these two modes during an active connection is possible: in Mirror mode, close the mirror window and start an Audio mode connection, switch back by initiating a Mirror mode connection. Note that Apple DRM (as in Apple TV app content on the client) cannot be decrypted by UxPlay, and (unlike with a true AppleTV), the client cannot run a http connection on the server instead of streaming content from one on the client.
UxPlay uses GStreamer Plugins for rendering audio and video, and does not offer the alternative Raspberry-Pi-specific audio and video renderers available in RPiPlay. It is tested on a number of systems, including (among others) Ubuntu 20.04, Linux Mint 20.2, OpenSUSE 15.3, macOS 10.15, FreeBSD 13.0.
@@ -137,7 +137,7 @@When the Apple TV 2nd generation was introduced in 2010, it received support for the AirTunes protocol. However, because this device allowed playback of visual content, the protocol was extended and renamed AirPlay. It was now possible to stream photo slideshows and videos. Shortly after the release of the Apple TV 2nd generation, AirPlay support for iOS was included in the iOS 4.2 update. It seems like at that point, the audio stream was still actually using the same AirTunes 2 protocol as described above. The video and photo streams were added as a whole new protocol based on HTTP, pretty much independent from the audio stream. Soon, the first curious developers began to investigate how it worked. Their conclusion was that visual content is streamed unencrypted.
In April 2011, a talented hacker extracted the AirPlay private key from an AirPort Express. This meant that finally, third-party developers were able to also build AirPlay receiver (server) programs.
For iOS 5, released in 2011, Apple added a new protocol to the AirPlay suite: AirPlay mirroring. Initial investigators found this new protocol used encryption in order to protect the transferred video data.
-By 2012, most of AirPlay’s protocols had been reverse-engineered and documented. At this point, audio still used the AirTunes 2 protocol from around 2008, video, photos and mirroring still used their respective protocols in an unmodified form, so you could still speak of AirPlay 1 (building upon AirTunes 2). The Airplay server running on the Apple TV reported as version 130. The setup of AirPlay mirroring used the xml format, in particular a stream.xml file. Additionally, it seems like the actual audio data is using the ALAC codec for audio-only (AirTunes 2) streaming and AAC for mirror audio. At least these different formats were used in later iOS versions.
+By 2012, most of AirPlay’s protocols had been reverse-engineered and documented (see also updated version). At this point, audio still used the AirTunes 2 protocol from around 2008, video, photos and mirroring still used their respective protocols in an unmodified form, so you could still speak of AirPlay 1 (building upon AirTunes 2). The Airplay server running on the Apple TV reported as version 130. The setup of AirPlay mirroring used the xml format, in particular a stream.xml file. Additionally, it seems like the actual audio data is using the ALAC codec for audio-only (AirTunes 2) streaming and AAC for mirror audio. At least these different formats were used in later iOS versions.
Sometime before iOS 9, the protocol for mirroring was slightly modified: Instead of the “stream.xml” API endpoint, the same information could also be querried in binary plist form, just by changing the API endpoint to “stream”, without any extension. I wasn’t able to figure out which of these was actually used by what specific client / server versions.
For iOS 9, Apple made considerable changes to the AirPlay protocol in 2015, including audio and mirroring. Apparently, the audio protocol was only slightly modified, and a minor change restored compatibility. For mirroring, an additional pairing phase was added to the connection establishment procedure, consisting of pair-setup and pair-verify calls. Seemingly, these were added in order to simplify usage with devices that are connected frequently. Pair-setup is used only the first time an iOS device connects to an AirPlay receiver. The generated cryptographic binding can be used for pair-verify in later sessions. Additionally, the stream / stream.xml endpoint was replaced with the info endpoint (only available as binary plist AFAICT). As of iOS 12, the protocol introduced with iOS 9 was still supported with only slight modifications, albeit as a legacy mode. While iOS 9 used two SETUP calls (one for general connection and mirroring video, and one for audio), iOS 12 legacy mode uses 3 SETUP calls (one for general connection (timing and events), one for mirroring video, one for audio).
The release of tvOS 10.2 broke many third-party AirPlay sender (client) programs in 2017. The reason was that it was now mandatory to perform device verification via a pin in order to stream content to an Apple TV. The functionality had been in the protocol before, but was not mandatory. Some discussion about the new scheme can be found here. A full specification of the pairing and authentication protocol was made available on GitHub. At that point, tvOS 10.2 reported as AirTunes/320.20.
diff --git a/README.md b/README.md index ccd321a..2eb3b57 100644 --- a/README.md +++ b/README.md @@ -16,8 +16,8 @@ sharing that window on screen-sharing applications such as Zoom) on a host running Linux, macOS, or other unix. UxPlay supports a "legacy" form of Apple's AirPlay Mirror protocol introduced in iOS 12; client devices running iOS/iPadOS 12 or later are supported, as is a (nonfree) Windows-based AirPlay-client software emulator, AirMyPC. Older (32-bit) client devices that can only run iOS 9.3 or iOS 10.3 are -currently only partially supported by UxPlay: reports indicate that -screen-mirroring video works, but a so-far not-understood protocol difference means the audio stream is not correctly decrypted. +currently partially supported by UxPlay: reports indicate that +screen-mirroring video works, audio is a work in progess. (Details of what is publically known about Apple's AirPlay2 protocol can be found [here](https://github.com/SteeBono/airplayreceiver/wiki/AirPlay2-Protocol) and [here](https://emanuelecozzi.net/docs/airplay2)). @@ -547,7 +547,7 @@ In April 2011, a talented hacker [extracted the AirPlay private key](http://www. For iOS 5, released in 2011, Apple added a new protocol to the AirPlay suite: AirPlay mirroring. [Initial investigators](https://www.aorensoftware.com/blog/2011/08/20/exploring-airplay-mirroring-internals/) found this new protocol used encryption in order to protect the transferred video data. -By 2012, most of AirPlay's protocols had been reverse-engineered and [documented](https://nto.github.io/AirPlay.html). At this point, audio still used the AirTunes 2 protocol from around 2008, video, photos and mirroring still used their respective protocols in an unmodified form, so you could still speak of AirPlay 1 (building upon AirTunes 2). The Airplay server running on the Apple TV reported as version 130. The setup of AirPlay mirroring used the xml format, in particular a stream.xml file. +By 2012, most of AirPlay's protocols had been reverse-engineered and [documented](https://nto.github.io/AirPlay.html) (see also [updated version](https://openairplay.github.io/airplay-spec)). At this point, audio still used the AirTunes 2 protocol from around 2008, video, photos and mirroring still used their respective protocols in an unmodified form, so you could still speak of AirPlay 1 (building upon AirTunes 2). The Airplay server running on the Apple TV reported as version 130. The setup of AirPlay mirroring used the xml format, in particular a stream.xml file. Additionally, it seems like the actual audio data is using the ALAC codec for audio-only (AirTunes 2) streaming and AAC for mirror audio. At least these different formats were used in [later iOS versions](https://github.com/espes/Slave-in-the-Magic-Mirror/issues/12#issuecomment-372380451). diff --git a/README.txt b/README.txt index 4a5816f..ff3be8e 100644 --- a/README.txt +++ b/README.txt @@ -21,11 +21,10 @@ macOS, or other unix. UxPlay supports a "legacy" form of Apple's AirPlay Mirror protocol introduced in iOS 12; client devices running iOS/iPadOS 12 or later are supported, as is a (nonfree) Windows-based AirPlay-client software emulator, AirMyPC. Older (32-bit) client devices -that can only run iOS 9.3 or iOS 10.3 are currently only partially -supported by UxPlay: reports indicate that screen-mirroring video works, -but a so-far not-understood protocol difference means the audio stream -is not correctly decrypted. (Details of what is publically known about -Apple's AirPlay2 protocol can be found +that can only run iOS 9.3 or iOS 10.3 are currently partially supported +by UxPlay: reports indicate that screen-mirroring video works, audio is +a work in progess. (Details of what is publically known about Apple's +AirPlay2 protocol can be found [here](https://github.com/SteeBono/airplayreceiver/wiki/AirPlay2-Protocol) and [here](https://emanuelecozzi.net/docs/airplay2)). @@ -746,11 +745,12 @@ found this new protocol used encryption in order to protect the transferred video data. By 2012, most of AirPlay's protocols had been reverse-engineered and -[documented](https://nto.github.io/AirPlay.html). At this point, audio -still used the AirTunes 2 protocol from around 2008, video, photos and -mirroring still used their respective protocols in an unmodified form, -so you could still speak of AirPlay 1 (building upon AirTunes 2). The -Airplay server running on the Apple TV reported as version 130. The +[documented](https://nto.github.io/AirPlay.html) (see also [updated +version](https://openairplay.github.io/airplay-spec)). At this point, +audio still used the AirTunes 2 protocol from around 2008, video, photos +and mirroring still used their respective protocols in an unmodified +form, so you could still speak of AirPlay 1 (building upon AirTunes 2). +The Airplay server running on the Apple TV reported as version 130. The setup of AirPlay mirroring used the xml format, in particular a stream.xml file. Additionally, it seems like the actual audio data is using the ALAC codec for audio-only (AirTunes 2) streaming and AAC for diff --git a/lib/global.h b/lib/global.h index af38e24..00f0fe3 100755 --- a/lib/global.h +++ b/lib/global.h @@ -5,12 +5,15 @@ #define GLOBAL_MODEL "AppleTV2,1" #define GLOBAL_VERSION "220.68" -/* use old protocol for AES key if User-Agent string is contained in these strings */ -/* replace xxx by any new User-Agent string as needed */ -#define OLD_PROTOCOL_AUDIO_CLIENT_LIST "AirMyPC/2.0;xxx" -#define OLD_PROTOCOL_VIDEO_CLIENT_LIST "AirMyPC/2.0;xxx" +/* use old protocol (unhashed AESkey for audio decryption if clients source version is not greater than 330.x.x */ +#define OLD_PROTOCOL_AUDIO_CLIENT_SOURCEVERSION "330.0.0" -#define DECRYPTION_TEST 0 +/* use old protocol for audio or video AES key if client's User-Agent string is contained in these strings */ +/* replace xxx by any new User-Agent string as needed */ +#define OLD_PROTOCOL_AUDIO_CLIENT_USER_AGENT_LIST "AirMyPC/2.0;xxx" +#define OLD_PROTOCOL_VIDEO_CLIENT_USER_AGENT_LIST "AirMyPC/2.0;xxx" + +#define DECRYPTION_TEST 2 #define MAX_HWADDR_LEN 6 diff --git a/lib/raop_buffer.c b/lib/raop_buffer.c index 8181c46..353af2d 100755 --- a/lib/raop_buffer.c +++ b/lib/raop_buffer.c @@ -152,8 +152,8 @@ raop_buffer_decrypt(raop_buffer_t *raop_buffer, unsigned char *data, unsigned ch #endif if (DECRYPTION_TEST) { - printf("%s\n", OLD_PROTOCOL_AUDIO_CLIENT_LIST); - printf("%d before %s", payload_size, utils_data_to_string(&data[12],16,16 )); + printf("encrypted header %s", utils_data_to_string(data,12,12)); + if (payload_size) printf("%d before %s", payload_size, utils_data_to_string(&data[12],16,16 )); } encryptedlen = payload_size / 16*16; memset(output, 0, payload_size); @@ -164,9 +164,17 @@ raop_buffer_decrypt(raop_buffer_t *raop_buffer, unsigned char *data, unsigned ch memcpy(output + encryptedlen, &data[12 + encryptedlen], payload_size - encryptedlen); *outputlen = payload_size; - if (DECRYPTION_TEST){ - if ( !(output[0] == 0x8d || output[0] == 0x8e || output[0] == 0x20)) printf("***ERROR AUDIO FRAME IS NOT AAC_ELD OR ALAC\n"); - printf("%d after %s\n", payload_size, utils_data_to_string(output,16,16 )); + if (payload_size && DECRYPTION_TEST){ + if ( !(output[0] == 0x8d || output[0] == 0x8e || output[0] == 0x81 || output[0] == 0x82 || output[0] == 0x20)) { + printf("***ERROR AUDIO FRAME IS NOT AAC_ELD OR ALAC\n"); + } + if (DECRYPTION_TEST == 2) { + printf("decrypted audio frame, len = %d\n", *outputlen); + printf("%s",utils_data_to_string(output,payload_size,16)); + printf("\n"); + } else { + printf("%d after %s\n", payload_size, utils_data_to_string(output,16,16 )); + } } #ifdef DUMP_AUDIO // Decrypted file diff --git a/lib/raop_handlers.h b/lib/raop_handlers.h index d6c526c..03264af 100755 --- a/lib/raop_handlers.h +++ b/lib/raop_handlers.h @@ -352,7 +352,10 @@ raop_handler_setup(raop_conn_t *conn, unsigned char aesiv[16]; unsigned char aeskey_old[16]; unsigned char aeskey[16]; + unsigned char *aeskey_audio = aeskey; + unsigned char *aeskey_video = aeskey; + logger_log(conn->raop->logger, LOGGER_DEBUG, "SETUP 1"); // First setup @@ -399,25 +402,38 @@ raop_handler_setup(raop_conn_t *conn, logger_log(conn->raop->logger, LOGGER_DEBUG, "16 byte aeskey after sha-256 hash with ecdh_secret:\n%s", str); free(str); - /* old-protocol clients such as AirMyPC use the unhashed key aeskey_old for both audio and video */ - /* OLD_PROTOCOL_AUDIO_CLIENT_LIST, OLD_PROTOCOL_VIDEO_CLIENT_LIST are defined in global.h */ - - const char * user_agent = http_request_get_header(request, "User-Agent"); - logger_log(conn->raop->logger, LOGGER_INFO, "Client identified as User-Agent: %s", user_agent); - unsigned char *aeskey_audio, *aeskey_video; - if (strstr(OLD_PROTOCOL_AUDIO_CLIENT_LIST,user_agent)) { /* old-protocol clients use the unhashed AES key */ - logger_log(conn->raop->logger, LOGGER_INFO, "This identifies client as using old protocol for AES audio key)"); - aeskey_audio = aeskey_old; - } else { - aeskey_audio = aeskey; + /* old-protocol clients such as AirMyPC use the unhashed key aeskey_old for both audio and video (ios9, ios10 support)*/ + /* clients with sourceVersion <= OLD_PROTOCOL_AUDIO_CLIENT_SOURCEVERSION use unhashed aeskey_old for audio decryption */ + /* this is also done for audio or video if clients User-Agent string is included in OLD_PROTOCOL_[AUDIO,VIDEO]_CLIENT_LIST (AirMyPC support)*/ + /* OLD_PROTOCOL_AUDIO_CLIENT_LIST, OLD_PROTOCOL_VIDEO_CLIENT_LIST, OLD_PROTOCOL_AUDIO_CLIENT_SOURCEVERSION are defined in global.h */ + + plist_t req_source_version_node = plist_dict_get_item(req_root_node, "sourceVersion"); + char *sourceVersion; + plist_get_string_val(req_source_version_node, &sourceVersion); + const char *user_agent = http_request_get_header(request, "User-Agent"); + logger_log(conn->raop->logger, LOGGER_INFO, "Client identified as User-Agent: %s, sourceVersion: %s", user_agent, sourceVersion); + +#ifdef OLD_PROTOCOL_AUDIO_CLIENT_SOURCEVERSION + char *end_ptr; + if (strtoul(sourceVersion, &end_ptr, 10) <= strtoul(OLD_PROTOCOL_AUDIO_CLIENT_SOURCEVERSION , &end_ptr, 10)) aeskey_audio = aeskey_old; +#endif + +#ifdef OLD_PROTOCOL_AUDIO_CLIENT_USER_AGENT_LIST + if (strstr(OLD_PROTOCOL_AUDIO_CLIENT_USER_AGENT_LIST, user_agent)) aeskey_audio = aeskey_old; +#endif + +#ifdef OLD_PROTOCOL_VIDEO_CLIENT_USER_AGENT_LIST + if (strstr(OLD_PROTOCOL_AUDIO_CLIENT_USER_AGENT_LIST, user_agent)) aeskey_video = aeskey_old; +#endif + + if (aeskey_audio == aeskey_old) { + logger_log(conn->raop->logger, LOGGER_INFO, "Client identifed as using old protocol (unhashed) AES audio key)"); } - if (strstr(OLD_PROTOCOL_VIDEO_CLIENT_LIST, user_agent)) { /* old-protocol clients use the unhashed AES key */ - logger_log(conn->raop->logger, LOGGER_INFO, "This identifies client as using old protocol for AES video key)"); - aeskey_video = aeskey_old; - } else { - aeskey_video = aeskey; + + if (aeskey_video == aeskey_old) { + logger_log(conn->raop->logger, LOGGER_INFO, "Client identifed as using old protocol (unhashed) AES video key)"); } - + // Time port uint64_t timing_rport; plist_t time_note = plist_dict_get_item(req_root_node, "timingPort"); diff --git a/renderers/audio_renderer_gstreamer.c b/renderers/audio_renderer_gstreamer.c index f707299..341cbda 100644 --- a/renderers/audio_renderer_gstreamer.c +++ b/renderers/audio_renderer_gstreamer.c @@ -193,6 +193,7 @@ void audio_renderer_render_buffer(raop_ntp_t *ntp, unsigned char* data, int data switch (renderer->ct) { case 8: /*AAC-ELD*/ valid = (data[0] == 0x8d || data[0] == 0x8e); + valid = valid || (data[0] == 0x81 || data[0] == 0x82); /* old protocol iOS 8, iOS 9 */ break; case 2: /*ALAC*/ valid = (data[0] == 0x20);