From 287babd2114c1fc44c1f013c2cf3d89dc4cfa28c Mon Sep 17 00:00:00 2001 From: fduncanh Date: Wed, 15 Dec 2021 16:47:13 -0500 Subject: [PATCH] rework identification of old-protocol clinets (after report about iOS9) --- README.html | 4 +-- README.md | 16 +++++------- README.txt | 33 +++++++++++++----------- lib/global.h | 6 ++++- lib/raop_handlers.h | 63 +++++++++++++++++++++++++++++---------------- 5 files changed, 73 insertions(+), 49 deletions(-) diff --git a/README.html b/README.html index 356c785..3d143da 100644 --- a/README.html +++ b/README.html @@ -1,6 +1,6 @@

UxPlay 1.44: AirPlay/AirPlay-Mirror server for Linux, macOS, and Unix.

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/macOS clients (iPads, iPhones, MacBooks, as well as certain third-party AirPlay-emulator clients on Windows, such as AirMyPC) 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, using Apple’s AirPlay Mirror protocol first available in iOS 5. (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, as well as certain third-party AirPlay-emulator clients on Windows, such as AirMyPC) 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, using Apple’s AirPlay Mirror protocol first available in iOS 5. (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.

@@ -63,7 +63,7 @@

Some extra GStreamer packages for special plugins may need to be installed (or reinstalled: a user using a Wayland display system as an alternative to X11 reported that after reinstalling Lubuntu 18.4, UxPlay would not work until gstreamer1.0-x was installed, presumably for Wayland’s X11-compatibility mode). Different distributions may break up GStreamer 1.x into packages in different ways; the packages listed above in the build instructions should bring in other required GStreamer packages as dependencies, but will not install all possible plugins.

5. Failure to decrypt ALL video and audio streams from a particular (older) client:

This triggers an error message, and will be due to use of an incorrect protocol for getting the AES decryption key from the client.

-

Modern Apple clients use a more-encrypted protocol than older ones. Which protocol is used by UxPlay depends on the client sourceVersion (reported by the client and now shown in the terminal output). Since UxPlay 1.45, to support the third-party Windows AirPlay-client emulator AirMyPC, which uses the old protocol and reports itself as having sourceVersion 280.33, the legacy protocol is used for clients reporting sourceVersion 280.x.x or older. This cutoff is set as OLD_PROTOCOL_CLIENT "280.33" in lib/global.h; change it there if necessary.

+

Modern Apple clients use a more-encrypted protocol than older ones. Which protocol is used by UxPlay depends on the client User-Agent string (reported by the client and now shown in the terminal output). Since UxPlay 1.45, to support the third-party Windows AirPlay-client emulator AirMyPC, which uses the old protocol and reports itself as User-Agent: “AirMyPC/2.0”. The legacy protocol is used for clients reporting a User-Agent string contained in OLD_PROTOCOL_AUDIO_CLIENT_LIST (for the audio AES key) and OLD_PROTOCOL_VIDEO_CLIENT_LIST (for the video AES key), defined inlib/global.h. You might need to add User-Agent strings for iOS clients running very old versions of iOS or iPadOS to one or both of these lists for them to work with UxPlay.

Usage:

Options:

-n server_name (Default: UxPlay); server_name@_hostname_ will be the name that appears offering AirPlay services to your iPad, iPhone etc, where hostname is the name of the server running uxplay. This will also now be the name shown above the mirror display (X11) window.

diff --git a/README.md b/README.md index c23b0b7..0f9b747 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,5 @@ - # UxPlay 1.44: AirPlay/AirPlay-Mirror server for Linux, macOS, and Unix. - 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](https://github.com/FDH2/UxPlay) (where development and user-assistance now takes place), although it initially was developed by @@ -11,9 +9,8 @@ from [RPiPlay](https://github.com/FD-/RPiPlay), which in turn derives from [shairplay](https://github.com/juhovh/shairplay), and [playfair](https://github.com/EstebanKubata/playfair). (The antimof site is mainly inactive, but periodically posts updates pulled from the [main UxPlay site](https://github.com/FDH2/UxPlay)). - -Its main use is to act like an AppleTV for screen-mirroring (with audio) of iOS/macOS clients -(iPads, iPhones, MacBooks, as well as certain third-party AirPlay-emulator clients on Windows, such as _AirMyPC_) in a window on the server display (with the possibility of +Its main use is to act like an AppleTV for screen-mirroring (with audio) of iOS/iPadOS/macOS clients +(iPhones, iPads, MacBooks, as well as certain third-party AirPlay-emulator clients on Windows, such as _AirMyPC_) 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, using Apple's AirPlay Mirror protocol first available in iOS 5. (Details of what is publically known about Apple's AirPlay2 protocol can be found [here](https://github.com/SteeBono/airplayreceiver/wiki/AirPlay2-Protocol) and @@ -278,10 +275,11 @@ other required GStreamer packages as dependencies, but will not install all poss This triggers an error message, and will be due to use of an incorrect protocol for getting the AES decryption key from the client. Modern Apple clients use a more-encrypted protocol than older ones. -Which protocol is used by UxPlay depends on the client _sourceVersion_ (reported by the client and now shown in the terminal output). Since UxPlay 1.45, -to support the third-party Windows AirPlay-client emulator _AirMyPC_, which uses the old protocol and reports itself as having sourceVersion 280.33, -the legacy protocol is used for clients reporting sourceVersion 280.x.x or older. This cutoff is set as ```OLD_PROTOCOL_CLIENT "280.33"``` -in ```lib/global.h```; change it there if necessary. +Which protocol is used by UxPlay depends on the client _User-Agent_ string (reported by the client and now shown in the terminal output). Since UxPlay 1.45, +to support the third-party Windows AirPlay-client emulator _AirMyPC_, which uses the old protocol and reports itself as User-Agent: "AirMyPC/2.0". +The legacy protocol is used for clients reporting a User-Agent string contained in OLD_PROTOCOL_AUDIO_CLIENT_LIST (for the audio AES key) and +OLD_PROTOCOL_VIDEO_CLIENT_LIST (for the video AES key), defined in```lib/global.h```. You might need to add User-Agent strings for iOS clients +running very old versions of iOS or iPadOS to one or both of these lists for them to work with UxPlay. # **Usage:** diff --git a/README.txt b/README.txt index 5d6eb0d..84fcb11 100644 --- a/README.txt +++ b/README.txt @@ -14,13 +14,13 @@ is mainly inactive, but periodically posts updates pulled from the [main UxPlay site](https://github.com/FDH2/UxPlay)). Its main use is to act like an AppleTV for screen-mirroring (with audio) -of iOS/macOS clients (iPads, iPhones, MacBooks, as well as certain -third-party AirPlay-emulator clients on Windows, such as *AirMyPC*) 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, using Apple's AirPlay Mirror protocol first -available in iOS 5. (Details of what is publically known about Apple's -AirPlay2 protocol can be found +of iOS/iPadOS/macOS clients (iPhones, iPads, MacBooks, as well as +certain third-party AirPlay-emulator clients on Windows, such as +*AirMyPC*) 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, using Apple's AirPlay Mirror +protocol first available in iOS 5. (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)). @@ -365,14 +365,17 @@ This triggers an error message, and will be due to use of an incorrect protocol for getting the AES decryption key from the client. Modern Apple clients use a more-encrypted protocol than older ones. -Which protocol is used by UxPlay depends on the client *sourceVersion* -(reported by the client and now shown in the terminal output). Since -UxPlay 1.45, to support the third-party Windows AirPlay-client emulator -*AirMyPC*, which uses the old protocol and reports itself as having -sourceVersion 280.33, the legacy protocol is used for clients reporting -sourceVersion 280.x.x or older. This cutoff is set as -`OLD_PROTOCOL_CLIENT "280.33"` in `lib/global.h`; change it there if -necessary. +Which protocol is used by UxPlay depends on the client *User-Agent* +string (reported by the client and now shown in the terminal output). +Since UxPlay 1.45, to support the third-party Windows AirPlay-client +emulator *AirMyPC*, which uses the old protocol and reports itself as +User-Agent: "AirMyPC/2.0". The legacy protocol is used for clients +reporting a User-Agent string contained in +OLD\_PROTOCOL\_AUDIO\_CLIENT\_LIST (for the audio AES key) and +OLD\_PROTOCOL\_VIDEO\_CLIENT\_LIST (for the video AES key), defined +in`lib/global.h`. You might need to add User-Agent strings for iOS +clients running very old versions of iOS or iPadOS to one or both of +these lists for them to work with UxPlay. **Usage:** ========== diff --git a/lib/global.h b/lib/global.h index b330f15..7d32757 100755 --- a/lib/global.h +++ b/lib/global.h @@ -5,7 +5,11 @@ #define GLOBAL_MODEL "AppleTV2,1" #define GLOBAL_VERSION "220.68" -#define OLD_PROTOCOL_CLIENT "280.33" +/* 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" + #define MAX_HWADDR_LEN 6 #endif diff --git a/lib/raop_handlers.h b/lib/raop_handlers.h index 25a8d9e..8337d51 100755 --- a/lib/raop_handlers.h +++ b/lib/raop_handlers.h @@ -350,7 +350,9 @@ raop_handler_setup(raop_conn_t *conn, // The first SETUP call that initializes keys and timing unsigned char aesiv[16]; + unsigned char aeskey_old[16]; unsigned char aeskey[16]; + logger_log(conn->raop->logger, LOGGER_DEBUG, "SETUP 1"); // First setup @@ -372,9 +374,9 @@ raop_handler_setup(raop_conn_t *conn, logger_log(conn->raop->logger, LOGGER_DEBUG, "ekey:\n%s", str); free (str); - int ret = fairplay_decrypt(conn->fairplay, (unsigned char*) ekey, aeskey); + int ret = fairplay_decrypt(conn->fairplay, (unsigned char*) ekey, aeskey_old); logger_log(conn->raop->logger, LOGGER_DEBUG, "fairplay_decrypt ret = %d", ret); - str = utils_data_to_string(aeskey, 16, 16); + str = utils_data_to_string(aeskey_old, 16, 16); logger_log(conn->raop->logger, LOGGER_DEBUG, "16 byte aeskey (fairplay-decrypted from ekey):\n%s", str); free(str); @@ -383,25 +385,42 @@ raop_handler_setup(raop_conn_t *conn, str = utils_data_to_string(ecdh_secret, X25519_KEY_SIZE, 16); logger_log(conn->raop->logger, LOGGER_DEBUG, "32 byte shared ecdh_secret:\n%s", str); free(str); + + unsigned char eaeskey[64] = {}; + memcpy(eaeskey, aeskey_old, 16); + sha_ctx_t *ctx = sha_init(); + sha_update(ctx, eaeskey, 16); + sha_update(ctx, ecdh_secret, 32); + sha_final(ctx, eaeskey, NULL); + sha_destroy(ctx); + memcpy(aeskey, eaeskey, 16); + + str = utils_data_to_string(aeskey, 16, 16); + logger_log(conn->raop->logger, LOGGER_DEBUG, "16 byte aeskey after sha-256 hash with ecdh_secret:\n%s", str); + free(str); - /* sha-512 hashing of aeskey is skipped where the client has sourceVersion <= OLD_PROTOCOL_CLIENT (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); - logger_log(conn->raop->logger, LOGGER_INFO, "client sourceVersion %s (no hash of aeskey if <= %s)", sourceVersion, OLD_PROTOCOL_CLIENT); - - char *end_ptr; - if (strtoul(sourceVersion, &end_ptr, 10) > strtoul(OLD_PROTOCOL_CLIENT , &end_ptr, 10)) { - unsigned char eaeskey[64] = {0}; - memcpy(eaeskey, aeskey, 16); - sha_ctx_t *ctx = sha_init(); - sha_update(ctx, eaeskey, 16); - sha_update(ctx, ecdh_secret, 32); - sha_final(ctx, eaeskey, NULL); - sha_destroy(ctx); - memcpy(aeskey, eaeskey, 16); - } - + /* old-protocol clients such as AirMyPC use the unhashed key eaeskey for both audio and video */ + /* it appears iOS9 uses the hashed key for video, not clear about audio */ + /* leave open the possibility of unhashed key for audio, hashed for video */ + /* need to establish when Apple started using the modern protocol */ + /* 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; + } + 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; + } + // Time port uint64_t timing_rport; plist_t time_note = plist_dict_get_item(req_root_node, "timingPort"); @@ -412,8 +431,8 @@ raop_handler_setup(raop_conn_t *conn, conn->raop_ntp = raop_ntp_init(conn->raop->logger, conn->remote, conn->remotelen, timing_rport); raop_ntp_start(conn->raop_ntp, &timing_lport); - conn->raop_rtp = raop_rtp_init(conn->raop->logger, &conn->raop->callbacks, conn->raop_ntp, conn->remote, conn->remotelen, aeskey, aesiv); - conn->raop_rtp_mirror = raop_rtp_mirror_init(conn->raop->logger, &conn->raop->callbacks, conn->raop_ntp, conn->remote, conn->remotelen, aeskey); + conn->raop_rtp = raop_rtp_init(conn->raop->logger, &conn->raop->callbacks, conn->raop_ntp, conn->remote, conn->remotelen, aeskey_audio, aesiv); + conn->raop_rtp_mirror = raop_rtp_mirror_init(conn->raop->logger, &conn->raop->callbacks, conn->raop_ntp, conn->remote, conn->remotelen, aeskey_video); plist_t res_event_port_node = plist_new_uint(conn->raop->port); plist_t res_timing_port_node = plist_new_uint(timing_lport);