[PATCH 4/4] sout: add airplay support
Alexander Lyon
arlyon at me.com
Tue Oct 9 00:47:52 CEST 2018
---
configure.ac | 5 +
modules/MODULES_LIST | 1 +
modules/stream_out/Makefile.am | 8 +
modules/stream_out/airplay/airplay.c | 2771 +++++++++++++++++++++++++
modules/stream_out/airplay/csrp/srp.c | 955 +++++++++
modules/stream_out/airplay/csrp/srp.h | 233 +++
6 files changed, 3973 insertions(+)
create mode 100644 modules/stream_out/airplay/airplay.c
create mode 100755 modules/stream_out/airplay/csrp/srp.c
create mode 100755 modules/stream_out/airplay/csrp/srp.h
diff --git a/configure.ac b/configure.ac
index 1627c12b79..e58a5113cd 100644
--- a/configure.ac
+++ b/configure.ac
@@ -4072,6 +4072,11 @@ AS_IF([test "${enable_gnutls}" != "no"], [
])
+dnl
+dnl Airplay plugin
+dnl
+PKG_ENABLE_MODULES_VLC([AIRPLAY], [stream_out_airplay], [libplist libplist >= 2.0.0], [Airplay support], [auto], [${GCRYPT_CFLAGS} ${SOCKET_LIBS}], [${LIBPLIST_LIBS} ${GCRYPT_LIBS}])
+
dnl
dnl Taglib plugin
dnl
diff --git a/modules/MODULES_LIST b/modules/MODULES_LIST
index e1978cc6e4..95fce93fda 100644
--- a/modules/MODULES_LIST
+++ b/modules/MODULES_LIST
@@ -367,6 +367,7 @@ $Id$
* stats: Stats encoder function
* stereo_widen: Enhances stereo effect
* stl: EBU STL decoder
+ * stream_out_airplay: AirPlay streaming output module
* stream_out_autodel: monitor mux inputs and automatically add/delete streams
* stream_out_bridge: "exchange" streams between sout instances. To be used with VLM
* stream_out_chromaprint: Audio fingerprinter
diff --git a/modules/stream_out/Makefile.am b/modules/stream_out/Makefile.am
index 24a7a6db82..032c329a06 100644
--- a/modules/stream_out/Makefile.am
+++ b/modules/stream_out/Makefile.am
@@ -73,6 +73,14 @@ libstream_out_chromaprint_plugin_la_LIBADD = $(CHROMAPRINT_LIBS)
EXTRA_LTLIBRARIES += libstream_out_chromaprint_plugin.la
sout_LTLIBRARIES += $(LTLIBstream_out_chromaprint)
+# AirPlay plugin
+if HAVE_GCRYPT
+libstream_out_airplay_plugin_la_SOURCES = stream_out/airplay/airplay.c stream_out/airplay/csrp/srp.c stream_out/airplay/csrp/srp.h
+libstream_out_airplay_plugin_la_CFLAGS = $(GCRYPT_CFLAGS) $(LIBPLIST_CFLAGS) $(AM_CFLAGS) $(CFLAGS_stream_out_airplay)
+libstream_out_airplay_plugin_la_LIBADD = $(GCRYPT_LIBS) -lplist $(LIBS_stream_out_airplay)
+sout_LTLIBRARIES += libstream_out_airplay_plugin.la
+endif
+
# Chromecast plugin
SUFFIXES += .proto .pb.cc
diff --git a/modules/stream_out/airplay/airplay.c b/modules/stream_out/airplay/airplay.c
new file mode 100644
index 0000000000..b7db2160c5
--- /dev/null
+++ b/modules/stream_out/airplay/airplay.c
@@ -0,0 +1,2771 @@
+/*****************************************************************************
+ * airplay.c: Airplay v1 Streaming Support
+ *****************************************************************************
+ * Copyright (C) 2018 VLC authors and VideoLAN
+ *
+ * Author: Alexander Lyon <arlyon at me.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+/*****************************************************************************
+ * Preamble
+ *****************************************************************************/
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <vlc_common.h>
+#include <vlc_dialog.h>
+#include <vlc_fixups.h>
+#include <vlc_httpd.h>
+#include <vlc_keystore.h>
+#include <vlc_md5.h>
+#include <vlc_memstream.h>
+#include <vlc_network.h>
+#include <vlc_plugin.h>
+#include <vlc_sout.h>
+#include <vlc_strings.h>
+#include <vlc_stream.h>
+#include <vlc_url.h>
+
+#include <assert.h>
+#include <errno.h>
+#include <gcrypt.h>
+#include <limits.h>
+#include <plist/plist.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "csrp/srp.h"
+
+typedef struct
+{
+ char *psz_deviceid; // mac address
+ char *psz_model; // model name
+ uint64_t i_features; // bit field
+} server_info_t;
+
+typedef struct
+{
+ vlc_mutex_t lock;
+ int i_authenticated_fd;
+ bool b_join;
+ vlc_object_t *p_module;
+} ping_ctx_t;
+
+typedef struct
+{
+ char *psz_bonjour_hostname;
+ char *psz_ipv4_ip;
+ gcry_sexp_t *p_sexp_eddsa_keypair;
+
+ uint16_t i_port;
+ uint64_t i_features;
+
+ vlc_keystore *p_keystore;
+ server_info_t *p_server_info;
+
+ // internal http server
+ char *psz_m3u8;
+ vlc_mutex_t lock_m3u8;
+ httpd_host_t *p_httpd_host;
+ httpd_file_t *p_m3u8_file;
+ sout_stream_t *p_out;
+
+ // auth
+ ping_ctx_t *p_ping_ctx;
+ vlc_thread_t *p_ping_thread;
+
+ // this is used in the ping thread
+ // it is unsafe to make requests over
+ // this fd when the ping thread is running
+ int i_authenticated_fd;
+
+ // stream stuff
+ vlc_array_t *p_streams;
+ vlc_array_t *p_streams_out;
+ bool b_souts_changed;
+ bool b_out_force_reload;
+ bool b_perf_warning_shown;
+ bool b_has_video;
+ bool b_airplay_flushing;
+} sout_stream_sys_t;
+
+typedef struct sout_stream_id_sys_t
+{
+ es_format_t fmt;
+ struct sout_stream_id_sys_t *p_sub_id;
+ bool flushed;
+} sout_stream_id_sys_t;
+
+#define SOUT_CFG_PREFIX "sout-airplay-"
+#define AIRPLAY_USER_AGENT "VLC "VERSION
+#define AIRPLAY_USERNAME "VLC_AIRPLAY"
+
+#if 0
+#define HARDCODED_ED_PUBLIC "f81fb54a825fced95eb033afcd64314075abfb0abd20a970892503436f34b863"
+#define HARDCODED_ED_PRIVATE "3b26516fb3dc88eb181b9ed73f0bcd52bcd6b4c788e4bcaf46057fd078bee073"
+#define HARDCODED_CURVE_PUBLIC "05439b27f204f6298d1250eed5b8055cdae23025f878759a836b28646d18505a"
+#define HARDCODED_CURVE_PRIVATE "5394cd7df72cad8ad9eb71ab02d1a24bdaf72e9744d8ad5aa6179d0f9548d858"
+#endif
+
+#define FEATURE_VIDEO 0x1
+#define FEATURE_PHOTO 0x2
+#define FEATURE_VIDEO_FAIRPLAY 0x4
+#define FEATURE_VIDEO_VOLUME_CONTROL 0x8
+#define FEATURE_VIDEO_HTTP_LIVE_STREAMS 0x10
+#define FEATURE_SLIDESHOW 0x20
+// nothing for bit 6
+#define FEATURE_SCREEN 0x80
+#define FEATURE_SCREEN_ROTATE 0x100
+#define FEATURE_AUDIO 0x200
+// nothing for bit 10
+#define FEATURE_AUDIO_REDUNDANT 0x800
+#define FEATURE_FAIRPLAY_SECURE_AUTH 0x1000
+#define FEATURE_PHOTO_CACHING 0x2000
+
+#define TRANSCODING_NONE 0x0
+#define TRANSCODING_VIDEO 0x1
+#define TRANSCODING_AUDIO 0x2
+
+/*****************************************************************************
+ * Prototypes
+ *****************************************************************************/
+
+// Module Callbacks
+
+static int Open( vlc_object_t * );
+
+static void Close( vlc_object_t * );
+
+static void *Add( sout_stream_t *, const es_format_t * );
+
+static void Del( sout_stream_t *, void * );
+
+static int Send( sout_stream_t *, void *, block_t * );
+
+static void Flush( sout_stream_t *, void * );
+
+// Authentication
+
+static int AirplayVerify( vlc_object_t *p_this, int *i_authenticated_fd );
+
+static int AirplayPair( vlc_object_t *p_this, char *psz_username, uint8_t *p_bytes_ed_pub, size_t i_length_ed_pub );
+
+#ifdef AIRPLAY_DIGEST
+static bool airplay_RequiresDeviceVerification( vlc_object_t *p_this );
+
+static char *airplay_GenerateDigest( vlc_object_t *p_this, const char *psz_realm, const char *psz_nonce,
+ const char *psz_password, const char *psz_method, const char *psz_uri );
+
+static int airplay_StoreDigest( vlc_object_t *p_this );
+#endif
+
+// Network
+
+static int16_t DoHttp( vlc_object_t *p_this, int *pi_fd, const char *psz_method, const char *psz_path,
+ const char *psz_content_type, const char *psz_body, const size_t i_content_length,
+ const vlc_dictionary_t *p_req_headers, char **ppsz_resp_body,
+ size_t *pi_resp_content_length, vlc_dictionary_t *p_resp_headers );
+
+static int16_t ParseStatusCode( vlc_object_t *p_this, int i_fd );
+
+static int ParseHeaders( vlc_object_t *p_this, int i_fd, vlc_dictionary_t *headers );
+
+#ifdef AIRPLAY_DIGEST
+static int ParseWWWAuthenticate( char *psz_www_authenticate, char **ppsz_realm_value, char **ppsz_nonce_value );
+#endif
+
+static bool PortIsOpen( vlc_object_t *p_this, char *psz_host, uint16_t i_port );
+
+// Utility
+
+static server_info_t *GetServerInfo( vlc_object_t *p_this );
+
+static inline bool HasFeature( server_info_t *p_info, uint64_t i_feature );
+
+static int StartPlayback( vlc_object_t *p_this, const char *psz_uri );
+
+static int StopPlayback( vlc_object_t *p_this, bool close_connection );
+
+static void *PingThread( void *obj );
+
+static int startSoutChain( vlc_object_t *p_this, vlc_array_t *new_streams, const char *psz_sout_template );
+
+static void stopSoutChain( sout_stream_t *p_stream );
+
+static int m3u8Callback( httpd_file_sys_t *p_args, httpd_file_t *f,
+ uint8_t const *p_request, uint8_t **pp_data, int *pi_data );
+
+static sout_stream_id_sys_t *GetSubId( const sout_stream_t *p_stream, const sout_stream_id_sys_t *id, bool update );
+
+static void FreeSys( sout_stream_sys_t *p_sys );
+
+static void FreeServerInfo( server_info_t **pp_info );
+
+static int64_t vlc_strtoi( const char *str );
+
+static void ReverseBuffer( uint8_t *buffer, size_t length );
+
+static void DelInternal( sout_stream_t *p_stream, void *_id, bool b_reset_config );
+
+static bool IsFlushing( sout_stream_t *p_stream );
+
+static bool UpdateOutput( const sout_stream_t *p_stream );
+
+static bool CanDecodeAudio( vlc_fourcc_t codec );
+
+static bool CanDecodeVideo( vlc_fourcc_t codec );
+
+// Cryptography
+
+static int GetOrCreateEd25519( vlc_object_t *p_this, gcry_sexp_t **pp_sexp_ed );
+
+static int CreateCurve25519( vlc_object_t *p_this, gcry_sexp_t *p_sexp_curve );
+
+static void CalculatePairAESKeyIV( const struct srp_user *p_user, uint8_t **p_bytes_aes_key,
+ uint8_t **p_bytes_aes_IV, size_t *p_i_length_aes_key_IV );
+
+static void CalculateVerifyAESKeyIV( const uint8_t *p_bytes_curve_shared, size_t i_length_curve_shared,
+ uint8_t **p_bytes_aes_key, uint8_t **p_bytes_aes_IV,
+ size_t *p_i_length_aes_key_IV );
+
+static void EncryptEd25519( const struct srp_user *p_user, uint8_t *p_bytes_ed25519, size_t i_length_ed25519,
+ uint8_t **p_bytes_epk, uint8_t **p_bytes_authTag );
+
+static gcry_error_t CalculateAESSignature( const uint8_t *p_bytes_atv_challenge, size_t i_length_atv_challenge,
+ const uint8_t *p_bytes_ed_signature, size_t i_length_ed_signature,
+ const uint8_t *p_bytes_curve_shared, size_t i_length_curve_shared,
+ uint8_t **pp_bytes_aes_signature, size_t *p_i_length_aes_signature );
+
+static gcry_error_t Curve25519DiffieHellman( const uint8_t *p_bytes_a, size_t i_length_a, const uint8_t *p_bytes_B,
+ size_t i_length_B, uint8_t **pp_bytes_shared, size_t *p_i_length_shared );
+
+static void EdSignConcatenatedBytes( gcry_sexp_t sexp_ed_keypair, const uint8_t *p_bytes_a,
+ size_t i_length_a,
+ const uint8_t *p_bytes_b, size_t i_length_b,
+ uint8_t **p_bytes_ed_signature, size_t *i_length_ed_signature );
+
+static gcry_error_t CurveDecompress( gcry_mpi_t mpi_compressed, gcry_mpi_t *mpi_uncompressed );
+
+/*****************************************************************************
+ * Module descriptor
+ *****************************************************************************/
+
+#define PERF_TEXT N_( "Performance warning" )
+#define PERF_LONGTEXT N_( "Display a performance warning when transcoding" )
+
+vlc_module_begin()
+ set_shortname( N_( "AirPlay" ) )
+ set_description( N_( "AirPlay stream output" ) )
+ set_capability( "sout stream", 0 )
+ add_shortcut( "airplay" )
+ set_category( CAT_SOUT )
+ set_subcategory( SUBCAT_SOUT_STREAM )
+ set_callbacks( Open, Close )
+ add_string( SOUT_CFG_PREFIX"ip", NULL, NULL, NULL, false )
+ change_private()
+ add_integer( SOUT_CFG_PREFIX"port", NULL, NULL, NULL, false )
+ change_private()
+ add_integer( SOUT_CFG_PREFIX"show-perf-warning", 1, PERF_TEXT, PERF_LONGTEXT, true )
+ change_private()
+vlc_module_end()
+
+static const char *const ppsz_sout_options[] = {
+ "ip",
+ "port",
+ NULL
+};
+
+/*****************************************************************************
+ * Module Callbacks
+ *****************************************************************************/
+
+static int Open( vlc_object_t *p_this )
+{
+ gcry_check_version( "1.8.2" );
+ int return_error = VLC_EGENERIC;
+
+ sout_stream_t *p_stream = (sout_stream_t *)p_this;
+ sout_stream_sys_t *p_sys = vlc_obj_calloc( p_this, 1, sizeof( *p_sys ) );
+ if ( p_sys == NULL )
+ {
+ return_error = VLC_ENOMEM;
+ goto error;
+ }
+
+ config_ChainParse( p_stream, SOUT_CFG_PREFIX, ppsz_sout_options, p_stream->p_cfg );
+
+ p_sys->b_has_video = false;
+ p_sys->i_authenticated_fd = 0;
+ p_sys->i_port = (uint16_t)var_GetInteger( p_stream, SOUT_CFG_PREFIX
+ "port" );
+ if ( p_sys->i_port == 0 )
+ {
+ msg_Err( p_this, "no port specified" );
+ goto error;
+ }
+ if ( ( p_sys->psz_bonjour_hostname = var_GetNonEmptyString( p_stream, SOUT_CFG_PREFIX
+ "ip" ) ) == NULL )
+ {
+ msg_Err( p_this, "no hostname specified" );
+ goto error;
+ }
+ p_sys->p_keystore = vlc_keystore_create( p_this );
+ vlc_mutex_init( &p_sys->lock_m3u8 );
+
+ if ( ( p_sys->p_streams = calloc( 1, sizeof( *p_sys->p_streams ) ) ) == NULL )
+ {
+ return_error = VLC_ENOMEM;
+ goto error;
+ }
+
+ if ( ( p_sys->p_streams_out = calloc( 1, sizeof( *p_sys->p_streams_out ) ) ) == NULL )
+ {
+ return_error = VLC_ENOMEM;
+ goto error;
+ }
+
+ vlc_array_init( p_sys->p_streams );
+ vlc_array_init( p_sys->p_streams_out );
+
+ p_stream->pf_add = Add;
+ p_stream->pf_del = Del;
+ p_stream->pf_send = Send;
+ p_stream->pf_flush = Flush;
+ p_stream->p_sys = p_sys;
+ p_stream->pace_nocontrol = true;
+
+ // get ipv4 address accessible by apple TV
+ struct addrinfo *res = NULL;
+ struct addrinfo hints = {
+ .ai_family = AF_INET,
+ .ai_socktype = SOCK_STREAM,
+ };
+ vlc_getaddrinfo( p_sys->psz_bonjour_hostname, 7000, &hints, &res );
+
+ char *psz_ipv4_ip = calloc( NI_MAXNUMERICHOST, sizeof( *psz_ipv4_ip ) );
+ int port = 7000;
+
+ vlc_getnameinfo( res->ai_addr, res->ai_addrlen, psz_ipv4_ip, NI_MAXNUMERICHOST, &port, NI_NUMERICHOST );
+ int ipv4_fd = net_ConnectTCP( p_this, psz_ipv4_ip, port );
+ net_GetSockAddress( ipv4_fd, psz_ipv4_ip, &port );
+ p_sys->psz_ipv4_ip = psz_ipv4_ip;
+ net_Close( ipv4_fd );
+
+ if ( ( p_sys->p_server_info = GetServerInfo( p_this ) ) == NULL )
+ {
+ msg_Err( p_this, "Unable to get server info, aborting." );
+ goto error;
+ }
+
+ if ( ( return_error = AirplayVerify( p_this, &p_sys->i_authenticated_fd ) ) != VLC_SUCCESS )
+ {
+ msg_Err( p_this, "Could not authenticate with apple tv, aborting." );
+ goto error;
+ }
+
+ return HasFeature( p_sys->p_server_info, FEATURE_VIDEO | FEATURE_VIDEO_HTTP_LIVE_STREAMS )
+ ? VLC_SUCCESS : VLC_EGENERIC;
+
+ error:
+ if ( p_sys != NULL )
+ {
+ if ( p_sys->p_ping_ctx != NULL ) StopPlayback( p_this, true );
+ FreeSys( p_sys );
+ }
+ return return_error;
+}
+
+static void Close( vlc_object_t *p_this )
+{
+ sout_stream_t *p_stream = (sout_stream_t *)p_this;
+ sout_stream_sys_t *p_sys = p_stream->p_sys;
+
+ assert(
+ vlc_array_count( p_sys->p_streams ) == 0 &&
+ vlc_array_count( p_sys->p_streams_out ) == 0
+ );
+
+ if ( p_sys->p_ping_ctx != NULL ) StopPlayback( p_this, true );
+ stopSoutChain(p_stream);
+ FreeSys( p_sys );
+}
+
+static void *Add( sout_stream_t *p_stream, const es_format_t *p_fmt )
+{
+ sout_stream_sys_t *p_sys = p_stream->p_sys;
+
+ if ( !HasFeature( p_sys->p_server_info, FEATURE_VIDEO ) && p_fmt->i_cat != AUDIO_ES )
+ {
+ return NULL;
+ }
+
+ sout_stream_id_sys_t *p_sys_id = calloc( 1, sizeof( *p_sys_id ) );
+ if ( p_sys_id != NULL )
+ {
+ es_format_Copy( &p_sys_id->fmt, p_fmt );
+ p_sys_id->flushed = false;
+
+ vlc_array_append( p_sys->p_streams, p_sys_id );
+ p_sys->b_souts_changed = true;
+ }
+
+ return p_sys_id;
+}
+
+static void Del( sout_stream_t *p_stream, void *_id )
+{
+ sout_stream_id_sys_t *id = (sout_stream_id_sys_t *)( _id );
+ DelInternal( p_stream, id, true );
+}
+
+static int Send( sout_stream_t *p_stream, void *_id, block_t *p_buffer )
+{
+ sout_stream_sys_t *p_sys = p_stream->p_sys;
+ sout_stream_id_sys_t *id = (sout_stream_id_sys_t *)( _id );
+
+
+ if ( IsFlushing( p_stream ) )
+ {
+ block_ChainRelease( p_buffer );
+ return VLC_SUCCESS;
+ }
+
+ // get sub id
+ sout_stream_id_sys_t *p_sub_id = GetSubId( p_stream, id, true );
+ if ( p_sub_id == NULL )
+ {
+ block_ChainRelease( p_buffer );
+ return VLC_EGENERIC;
+ }
+
+ int error = sout_StreamIdSend( p_sys->p_out, p_sub_id, p_buffer );
+ if ( error != VLC_SUCCESS )
+ {
+ DelInternal( p_stream, id, false );
+ }
+
+ return error;
+}
+
+static void Flush( sout_stream_t *p_stream, void *_id )
+{
+ sout_stream_sys_t *p_sys = p_stream->p_sys;
+ sout_stream_id_sys_t *id = (sout_stream_id_sys_t *)( _id );
+
+ sout_stream_id_sys_t *p_sub_id = GetSubId( p_stream, id, false );
+ if ( p_sub_id == NULL )
+ return;
+ p_sub_id->flushed = true;
+
+ if ( !p_sys->b_airplay_flushing )
+ {
+ p_sys->b_airplay_flushing = true;
+ stopSoutChain( p_stream );
+ p_sys->b_out_force_reload = p_sys->b_souts_changed = true;
+ }
+}
+
+/*****************************************************************************
+ * Authentication
+ *****************************************************************************/
+
+static int AirplayVerify( vlc_object_t *p_this, int *i_authenticated_fd )
+{
+ sout_stream_t *p_stream = (sout_stream_t *)p_this;
+ sout_stream_sys_t *p_sys = p_stream->p_sys;
+
+ vlc_dictionary_t keep_alive_headers;
+ vlc_dictionary_init( &keep_alive_headers, 1 );
+ vlc_dictionary_insert( &keep_alive_headers, "Connection", "Keep-Alive" );
+
+ gcry_error_t gcry_error = 0;
+ int vlc_error = VLC_SUCCESS;
+ int16_t i_status;
+ int i_fd = 0;
+
+ uint8_t *p_bytes_curve_priv = NULL;
+ size_t i_length_curve_priv;
+ uint8_t *p_bytes_curve_pub = NULL;
+ size_t i_length_curve_pub;
+ uint8_t *p_bytes_curve_shared = NULL;
+ size_t i_length_curve_shared;
+ uint8_t *p_bytes_ed_signature = NULL;
+ size_t i_length_ed_signature;
+ uint8_t *p_bytes_aes_signature = NULL;
+ size_t i_length_aes_signature;
+ uint8_t *p_bytes_response = NULL;
+ size_t i_length_response;
+
+ gcry_mpi_t mpi_ed_pub = NULL;
+ gcry_mpi_t mpi_curve_pub_compressed = NULL;
+ gcry_mpi_t mpi_curve_pub = gcry_mpi_new( 0 );
+ gcry_mpi_t mpi_curve_priv = NULL;
+ gcry_sexp_t sexp_curve_keypair = NULL;
+
+ // get or create Ed25519 pair
+
+ if ( p_sys->p_sexp_eddsa_keypair == NULL )
+ {
+ vlc_error = GetOrCreateEd25519( p_this, &p_sys->p_sexp_eddsa_keypair );
+ }
+
+ if ( vlc_error != VLC_SUCCESS )
+ {
+ msg_Err( p_this, "Could not generate Ed25519 key." );
+ vlc_error = VLC_EGENERIC;
+ goto error;
+ }
+
+ if ( ( gcry_error = gcry_sexp_extract_param( *p_sys->p_sexp_eddsa_keypair, NULL, "q", &mpi_ed_pub, NULL ) ) )
+ {
+ msg_Err( p_this, "curve params failed: %s/%s", gcry_strsource( gcry_error ), gcry_strerror( gcry_error ) );
+ vlc_error = VLC_EGENERIC;
+ goto error;
+ }
+
+ // generate Curve ephemeral pair
+
+ if ( ( vlc_error = CreateCurve25519( p_this, &sexp_curve_keypair ) ) != VLC_SUCCESS )
+ {
+ msg_Err( p_this, "Could not generate Curve25519 key." );
+ vlc_error = VLC_EGENERIC;
+ goto error;
+ }
+
+ if ( ( gcry_error = gcry_sexp_extract_param( sexp_curve_keypair, NULL, "qd",
+ &mpi_curve_pub_compressed, &mpi_curve_priv, NULL ) ) )
+ {
+ msg_Err( p_this, "curve params failed: %s/%s", gcry_strsource( gcry_error ), gcry_strerror( gcry_error ) );
+ vlc_error = VLC_EGENERIC;
+ goto error;
+ }
+
+ if ( ( gcry_error = CurveDecompress( mpi_curve_pub_compressed, &mpi_curve_pub ) ) )
+ {
+ msg_Err( p_this, "could not decompress curve public key" );
+ vlc_error = VLC_EGENERIC;
+ goto error;
+ }
+
+ bool b_step_one = false;
+ uint8_t *p_bytes_step_one = NULL;
+ size_t i_length_step_one;
+
+ while ( b_step_one == false )
+ {
+ uint8_t *p_bytes_curve_pub = NULL;
+ size_t i_length_curve_pub;
+ uint8_t *p_bytes_ed_pub = NULL;
+ size_t i_length_ed_pub;
+ uint8_t *p_bytes_initialize = NULL;
+ size_t i_length_initialize;
+
+
+ gcry_mpi_aprint( GCRYMPI_FMT_USG, &p_bytes_curve_pub, &i_length_curve_pub, mpi_curve_pub );
+ ReverseBuffer( p_bytes_curve_pub, i_length_curve_pub );
+ gcry_mpi_aprint( GCRYMPI_FMT_USG, &p_bytes_ed_pub, &i_length_ed_pub, mpi_ed_pub );
+
+ // build octet stream - 01 00 00 00 ~ curve pub key ~ eddsa pub key
+ if ( i_length_curve_pub + i_length_ed_pub != 64 )
+ {
+ msg_Err( p_this, "Verify Octet Stream Invalid" );
+ free( p_bytes_curve_pub );
+ free( p_bytes_ed_pub );
+ goto error;
+ }
+
+ i_length_initialize = 4 + i_length_curve_pub + i_length_ed_pub;
+ p_bytes_initialize = calloc( i_length_initialize, sizeof( *p_bytes_initialize ) );
+ p_bytes_initialize[0] = 0x01;
+ p_bytes_initialize[1] = 0x00;
+ p_bytes_initialize[2] = 0x00;
+ p_bytes_initialize[3] = 0x00;
+ memcpy( p_bytes_initialize + 4, p_bytes_curve_pub, i_length_curve_pub );
+ memcpy( p_bytes_initialize + 4 + i_length_curve_pub, p_bytes_ed_pub, i_length_ed_pub );
+
+ i_status = DoHttp( p_this, &i_fd, "POST", "/pair-verify", "application/octet-stream",
+ (const char *)p_bytes_initialize, i_length_initialize, &keep_alive_headers,
+ (char **)&p_bytes_step_one, &i_length_step_one, NULL );
+
+ free( p_bytes_curve_pub );
+ free( p_bytes_initialize );
+
+ if ( i_status == 200 )
+ {
+ b_step_one = true;
+ } else
+ {
+ free( p_bytes_step_one );
+ net_Close( i_fd );
+ i_fd = 0;
+
+ if ( ( vlc_error = AirplayPair( p_this, "username", p_bytes_ed_pub, i_length_ed_pub ) ) != VLC_SUCCESS )
+ {
+ vlc_dialog_display_error( p_this, _("Invalid"), _("Invalid code") );
+ free( p_bytes_ed_pub );
+ goto error;
+ }
+ }
+
+ free( p_bytes_ed_pub );
+ }
+
+ // step 2: create a signature with the Ed25519 private key and send to Receiver.
+
+ const size_t i_length_atv_curve_pub = 32;
+ const size_t i_length_atv_challenge = 64;
+ uint8_t *p_bytes_atv_curve_pub = p_bytes_step_one;
+ uint8_t *p_bytes_atv_challenge = p_bytes_step_one + 32;
+
+ // curve 25519 diffie helman -> <shared>
+ // curve 25519 is stored backwards so we need to reverse the buffers..
+
+ gcry_mpi_aprint( GCRYMPI_FMT_USG, &p_bytes_curve_priv, &i_length_curve_priv, mpi_curve_priv );
+ ReverseBuffer( p_bytes_curve_priv, i_length_curve_priv );
+
+ gcry_mpi_aprint( GCRYMPI_FMT_USG, &p_bytes_curve_pub, &i_length_curve_pub, mpi_curve_pub );
+ ReverseBuffer( p_bytes_curve_pub, i_length_curve_pub );
+
+ Curve25519DiffieHellman( p_bytes_curve_priv, i_length_curve_priv, p_bytes_atv_curve_pub, i_length_atv_curve_pub,
+ &p_bytes_curve_shared, &i_length_curve_shared );
+
+
+ // Ed25519 sign(Sender Curve Pub ~ Receiver Curve Pub) -> <ed_signature>
+
+ EdSignConcatenatedBytes( *p_sys->p_sexp_eddsa_keypair, p_bytes_curve_pub, i_length_curve_pub, p_bytes_atv_curve_pub,
+ i_length_atv_curve_pub, &p_bytes_ed_signature, &i_length_ed_signature );
+
+ // create AES
+
+ if ( ( gcry_error = CalculateAESSignature( p_bytes_atv_challenge, i_length_atv_challenge, p_bytes_ed_signature,
+ i_length_ed_signature, p_bytes_curve_shared, i_length_curve_shared,
+ &p_bytes_aes_signature, &i_length_aes_signature ) ) )
+ {
+ msg_Err( p_this, "gcrypt error when calculating AES signature" );
+ vlc_error = VLC_EGENERIC;
+ goto error;
+ }
+
+ i_length_response = 4 + i_length_aes_signature;
+ p_bytes_response = calloc( i_length_response, sizeof( *p_bytes_response ) );
+ p_bytes_response[0] = 0x00;
+ p_bytes_response[1] = 0x00;
+ p_bytes_response[2] = 0x00;
+ p_bytes_response[3] = 0x00;
+ memcpy( p_bytes_response + 4, p_bytes_aes_signature, i_length_aes_signature );
+
+ i_status = DoHttp( p_this, &i_fd, "POST", "/pair-verify", "application/octet-stream",
+ (const char *)p_bytes_response, i_length_response, &keep_alive_headers, NULL, NULL, NULL );
+
+ if ( i_status != 200 )
+ {
+ msg_Err( p_this, "Could not verify." );
+ vlc_error = VLC_EGENERIC;
+ } else
+ {
+ if ( i_authenticated_fd != NULL )
+ {
+ *i_authenticated_fd = i_fd;
+ }
+ vlc_error = VLC_SUCCESS;
+ }
+
+ error:
+
+ vlc_dictionary_clear( &keep_alive_headers, NULL, NULL );
+ gcry_mpi_release( mpi_curve_pub );
+
+ if ( p_bytes_step_one != NULL ) free( p_bytes_step_one );
+ if ( mpi_ed_pub != NULL ) gcry_mpi_release( mpi_ed_pub );
+ if ( mpi_curve_pub_compressed != NULL ) gcry_mpi_release( mpi_curve_pub_compressed );
+ if ( mpi_curve_priv != NULL ) gcry_mpi_release( mpi_curve_priv );
+ if ( sexp_curve_keypair != NULL ) gcry_sexp_release( sexp_curve_keypair );
+ if ( p_bytes_curve_priv != NULL ) free( p_bytes_curve_priv );
+ if ( p_bytes_curve_pub != NULL ) free( p_bytes_curve_pub );
+ if ( p_bytes_curve_shared != NULL ) free( p_bytes_curve_shared );
+ if ( p_bytes_ed_signature != NULL ) free( p_bytes_ed_signature );
+ if ( p_bytes_aes_signature != NULL ) free( p_bytes_aes_signature );
+ if ( p_bytes_response != NULL ) free( p_bytes_response );
+
+ return vlc_error;
+}
+
+static int AirplayPair( vlc_object_t *p_this, char *psz_username, uint8_t *p_bytes_ed_pub, size_t i_length_ed_pub )
+{
+ int vlc_error = VLC_EGENERIC;
+ int16_t i_status;
+
+ uint8_t *p_bytes_plist = NULL;
+ char *psz_I = NULL;
+ char *psz_P = NULL;
+ uint32_t i_content_length;
+ size_t i_out_content_length;
+ vlc_dictionary_t response_headers;
+
+ int i_fd = 0;
+ vlc_dictionary_t keep_alive_headers;
+ vlc_dictionary_init( &keep_alive_headers, 1 );
+ vlc_dictionary_insert( &keep_alive_headers, "Connection", "Keep-Alive" );
+
+ // post to start pair
+ i_status = DoHttp( p_this, &i_fd, "POST", "/pair-pin-start", NULL, NULL, 0, &keep_alive_headers, NULL, NULL, NULL );
+
+ vlc_error = vlc_dialog_wait_login( p_this, &psz_I, &psz_P, NULL, psz_username, _("Enter Code"),
+ _("Enter the code on the selected AirPlay device into the password field.") );
+
+ if ( vlc_error == 0 )
+ msg_Err( p_this, "Verify was cancelled by user." );
+ if ( vlc_error < 1 )
+ goto error;
+
+ // post binary plist {method: "pin", user: I}
+
+ plist_t plist = plist_new_dict();
+ plist_t method = plist_new_string( "pin" );
+ plist_t user = plist_new_string( psz_I );
+ plist_dict_set_item( plist, "method", method );
+ plist_dict_set_item( plist, "user", user );
+ plist_to_bin( plist, (char **)&p_bytes_plist, &i_content_length );
+
+ i_status = DoHttp( p_this, &i_fd, "POST", "/pair-setup-pin", "application/x-apple-binary-plist",
+ (const char *)p_bytes_plist, i_content_length, &keep_alive_headers, (char **)&p_bytes_plist,
+ &i_out_content_length, &response_headers );
+ i_content_length = (uint32_t)i_out_content_length;
+
+ plist_free( plist );
+
+ plist_from_bin( (const char *)p_bytes_plist, i_content_length, &plist );
+ free( p_bytes_plist );
+ vlc_dictionary_clear( &response_headers, NULL, NULL );
+ p_bytes_plist = NULL;
+
+ uint8_t *p_bytes_B = NULL;
+ uint8_t *p_bytes_s = NULL;
+ uint64_t i_length_B;
+ uint64_t i_length_s;
+ plist_get_data_val( plist_dict_get_item( plist, "pk" ), (char **)&p_bytes_B, &i_length_B );
+ plist_get_data_val( plist_dict_get_item( plist, "salt" ), (char **)&p_bytes_s, &i_length_s );
+
+ // create user, calculate first ephemeral key (A) and client session key verifier (M1)
+
+ struct srp_user *p_user = apple_srp_user_new( psz_I, strlen( psz_I ), psz_P, strlen( psz_P ) );
+
+ uint8_t *p_bytes_A = NULL;
+ size_t i_A_length = 0;
+ srp_user_start_authentication( p_user, NULL, &p_bytes_A, &i_A_length );
+
+ uint8_t *p_bytes_M1 = NULL;
+ size_t i_M1_length = 0;
+ srp_user_process_challenge( p_user, p_bytes_s, i_length_s, p_bytes_B, i_length_B, &p_bytes_M1, &i_M1_length );
+
+ // post binary plist {pk: A, proof: M1}
+
+ plist = plist_new_dict();
+ plist_t srp6_A = plist_new_data( (const char *)p_bytes_A, i_A_length );
+ plist_t srp6_M1 = plist_new_data( (const char *)p_bytes_M1, i_M1_length );
+ plist_dict_set_item( plist, "pk", srp6_A );
+ plist_dict_set_item( plist, "proof", srp6_M1 );
+ plist_to_bin( plist, (char **)&p_bytes_plist, &i_content_length );
+
+ i_status = DoHttp( p_this, &i_fd, "POST", "/pair-setup-pin", "application/x-apple-binary-plist",
+ (const char *)p_bytes_plist, i_content_length, &keep_alive_headers, (char **)&p_bytes_plist,
+ &i_out_content_length, &response_headers );
+ i_content_length = (uint32_t)i_out_content_length;
+
+ plist_free( plist );
+
+ if ( i_status != 200 )
+ {
+ msg_Dbg( p_this, "Server authentication failed!\n" );
+ return VLC_EGENERIC;
+ }
+
+ // get proof (M2) from response
+
+ plist_from_bin( (const char *)p_bytes_plist, (uint32_t)i_content_length, &plist );
+ vlc_dictionary_clear( &response_headers, NULL, NULL );
+
+ uint8_t *p_bytes_M2 = NULL;
+ uint64_t i_length_M2;
+ plist_get_data_val( plist_dict_get_item( plist, "proof" ), (char **)&p_bytes_M2, &i_length_M2 );
+
+ plist_free( plist );
+ free( p_bytes_plist );
+ p_bytes_plist = NULL;
+
+ if ( !srp_user_verify_session( p_user, p_bytes_M2 ) )
+ {
+ msg_Dbg( p_this, "Server authentication failed!\n" );
+ return VLC_EGENERIC;
+ }
+
+ // encrypt Ed25519 public key and send it
+
+ uint8_t *p_epk;
+ uint8_t *p_authTag;
+ const uint8_t i_authTag_length = 16;
+
+ EncryptEd25519( p_user, p_bytes_ed_pub, i_length_ed_pub, &p_epk, &p_authTag );
+
+ // create and post plist
+
+ plist = plist_new_dict();
+ plist_t epk = plist_new_data( (const char *)p_epk, i_length_ed_pub );
+ plist_t authTag = plist_new_data( (const char *)p_authTag, i_authTag_length );
+ plist_dict_set_item( plist, "epk", epk );
+ plist_dict_set_item( plist, "authTag", authTag );
+ plist_to_bin( plist, (char **)&p_bytes_plist, &i_content_length );
+
+ i_status = DoHttp( p_this, &i_fd, "POST", "/pair-setup-pin", "application/x-apple-binary-plist",
+ (const char *)p_bytes_plist, i_content_length, &keep_alive_headers, NULL, NULL,
+ &response_headers );
+
+ plist_free( plist );
+ vlc_dictionary_clear( &response_headers, NULL, NULL );
+
+ if ( i_status == 200 )
+ vlc_error = VLC_SUCCESS;
+
+ error:
+ if ( p_bytes_plist != NULL ) free( p_bytes_plist );
+ if ( psz_I != NULL ) free( psz_I );
+ if ( psz_P != NULL ) free( psz_P );
+ return vlc_error;
+}
+
+#ifdef AIRPLAY_DIGEST
+/**
+ * Checks if the given connection requires device verification.
+ * @return True if device verification is necessary.
+ * @note Device verification is mandatory as of tvOS10.2
+ */
+static bool airplay_RequiresDeviceVerification( vlc_object_t *p_this )
+{
+ int64_t i_status = airplay_DoHttp( p_this, "POST", "/play", "application/x-apple-binary-plist", NULL, 0, NULL,
+ NULL, false );
+ return i_status == 403 ? true : false;
+}
+
+/**
+ * Creates the auth header for Airplay from a given nonce.
+ * @param psz_realm The realm for the request.
+ * @param psz_nonce The nonce from the server.
+ * @param psz_password The password (code).
+ * @param psz_method The request method.
+ * @param psz_uri The URI.
+ * @return The Authorization header.
+ * @note Calculated as follows -> hash(hash("username:realm:password")+hash("method:uri"))
+ */
+static char *airplay_GenerateDigest(
+ vlc_object_t *p_this,
+ const char *psz_realm,
+ const char *psz_nonce,
+ const char *psz_password,
+ const char *psz_method,
+ const char *psz_uri )
+{
+ struct md5_s p_first_md5;
+
+ InitMD5( &p_first_md5 );
+ AddMD5( &p_first_md5, AIRPLAY_USERNAME, strlen( AIRPLAY_USERNAME ) );
+ AddMD5( &p_first_md5, ":", 1 );
+ AddMD5( &p_first_md5, psz_realm, strlen( psz_realm ) );
+ AddMD5( &p_first_md5, ":", 1 );
+ AddMD5( &p_first_md5, psz_password, strlen( psz_password ) );
+ EndMD5( &p_first_md5 );
+
+ char *psz_first_hash = psz_md5_hash( &p_first_md5 );
+ msg_Dbg( p_this, "Calculated hash 1: "
+ AIRPLAY_USERNAME
+ ":%s:%s -> %s", psz_realm, psz_password, psz_first_hash );
+
+ struct md5_s p_second_md5;
+
+ InitMD5( &p_second_md5 );
+ AddMD5( &p_second_md5, psz_method, strlen( psz_method ) );
+ AddMD5( &p_second_md5, ":", 1 );
+ AddMD5( &p_second_md5, psz_uri, strlen( psz_uri ) );
+ EndMD5( &p_second_md5 );
+
+ char *psz_second_hash = psz_md5_hash( &p_second_md5 );
+ msg_Dbg( p_this, "Calculated hash 2: %s:%s -> %s", psz_method, psz_uri, psz_second_hash );
+
+ struct md5_s p_final_md5;
+
+ InitMD5( &p_final_md5 );
+ AddMD5( &p_final_md5, psz_first_hash, strlen( psz_first_hash ) );
+ AddMD5( &p_final_md5, ":", 1 );
+ AddMD5( &p_final_md5, psz_nonce, strlen( psz_nonce ) );
+ AddMD5( &p_final_md5, ":", 1 );
+ AddMD5( &p_final_md5, psz_second_hash, strlen( psz_second_hash ) );
+ EndMD5( &p_final_md5 );
+
+ char *response = psz_md5_hash( &p_final_md5 );
+ msg_Dbg( p_this, "Calculated final hash: %s:%s:%s -> %s", psz_first_hash, psz_nonce, psz_second_hash,
+ response );
+ return response;
+}
+
+static int airplay_StoreDigest( vlc_object_t *p_this )
+{
+ vlc_dictionary_t resp_headers;
+ int64_t i_status = airplay_DoHttp( p_this, "POST", "/play", NULL, NULL, 0, NULL, &resp_headers, false );
+ while ( i_status == 401 )
+ {
+ char *psz_username;
+ char *psz_code;
+ int err = vlc_dialog_wait_login( p_this, &psz_username, &psz_code, NULL, NULL, _("Enter Code"),
+ _("Enter the code on the selected AirPlay device into the password field.") );
+ if ( err < 1 ) return VLC_EGENERIC;
+ else free( psz_username );
+
+ char *psz_realm, *psz_nonce;
+ ParseWWWAuthenticate( (char *)vlc_dictionary_value_for_key( &resp_headers, "WWW-Authenticate" ),
+ &psz_realm,
+ &psz_nonce );
+
+ char *response = airplay_GenerateDigest( p_this, psz_realm, psz_nonce, psz_code, "POST", "/play" );
+
+ vlc_dictionary_clear( &resp_headers, NULL, NULL );
+ i_status = airplay_DoHttp( p_this, "POST", "/play", "application/x-apple-binary-plist", NULL, 0, NULL,
+ &resp_headers, false );
+
+ if ( i_status != 200 )
+ {
+ vlc_dialog_display_error( p_this, _("Invalid"), _("Invalid code") );
+ msg_Dbg( p_this, "Authentication failed: %d", i_status );
+ }
+
+ free( psz_realm );
+ free( psz_nonce );
+ free( psz_code );
+ free( response );
+ }
+}
+# endif
+
+/*****************************************************************************
+ * Network
+ *****************************************************************************/
+
+static int16_t
+DoHttp( vlc_object_t *p_this, int *pi_fd, const char *psz_method, const char *psz_path, const char *psz_content_type,
+ const char *psz_body, const size_t i_content_length, const vlc_dictionary_t *p_req_headers,
+ char **ppsz_resp_body, size_t *pi_resp_content_length, vlc_dictionary_t *p_resp_headers )
+{
+ sout_stream_t *p_stream = (sout_stream_t *)p_this;
+ sout_stream_sys_t *p_sys = p_stream->p_sys;
+ int i_fd;
+ bool free_headers = false;
+
+ if ( p_resp_headers == NULL )
+ {
+ p_resp_headers = calloc( 1, sizeof( *p_resp_headers ) );
+ free_headers = true;
+ }
+
+ if ( psz_body != NULL ) assert( psz_content_type != NULL );
+
+ struct vlc_memstream stream;
+ vlc_memstream_open( &stream );
+
+ vlc_memstream_printf( &stream, "%s %s HTTP/1.1\r\n", psz_method, psz_path );
+ vlc_memstream_puts( &stream, "User-Agent: "
+ AIRPLAY_USER_AGENT
+ "\r\n" );
+
+ if ( p_req_headers != NULL )
+ {
+ char **ppsz_keys = vlc_dictionary_all_keys( p_req_headers );
+ for ( int i = 0; i < vlc_dictionary_keys_count( p_req_headers ); ++i )
+ {
+ char *psz_key = ppsz_keys[i];
+ char *psz_value = (char *)vlc_dictionary_value_for_key( p_req_headers, ppsz_keys[i] );
+ vlc_memstream_printf( &stream, "%s: %s\r\n", psz_key, psz_value );
+ free( psz_key );
+ }
+ free( ppsz_keys );
+ }
+
+ if ( psz_body != NULL && psz_content_type != NULL )
+ {
+ vlc_memstream_printf( &stream, "Content-Type: %s\r\n", psz_content_type );
+ vlc_memstream_printf( &stream, "Content-Length: %zu\r\n", i_content_length );
+ vlc_memstream_puts( &stream, "\r\n" );
+ vlc_memstream_puts_len( &stream, psz_body, i_content_length );
+ } else
+ {
+ vlc_memstream_puts( &stream, "\r\n" );
+ }
+
+ if ( vlc_memstream_close( &stream ) )
+ {
+ return VLC_ENOMEM;
+ }
+
+ // if the pointer is null, or the supplied file descriptor is null, generate one.
+ if ( pi_fd == NULL || *pi_fd == 0 )
+ {
+ i_fd = net_ConnectTCP( p_stream, p_sys->psz_bonjour_hostname, p_sys->i_port );
+ if ( i_fd == -1 )
+ {
+ msg_Err( p_this, "Could not open connection with AppleTV" );
+ return VLC_EGENERIC;
+ }
+ } else
+ {
+ i_fd = *pi_fd;
+ }
+
+ // if the supplied fd was null, set the supplied fd to the generated one
+ if ( pi_fd != NULL && *pi_fd == 0 )
+ {
+ *pi_fd = i_fd;
+ }
+
+ ssize_t bytes_written = net_Write( p_this, i_fd, stream.ptr, stream.length );
+ free( stream.ptr );
+
+ if ( bytes_written != (ssize_t)stream.length )
+ {
+ msg_Err( p_this, "could not write to file descriptor %d", i_fd );
+ return VLC_EGENERIC;
+ }
+
+ int16_t i_status = ParseStatusCode( p_this, i_fd );
+ ParseHeaders( p_this, i_fd, p_resp_headers );
+
+ msg_Dbg( p_this, "Sent %zd bytes to %s on port %d (status %"
+ PRId16
+ ")",
+ bytes_written, p_sys->psz_bonjour_hostname, p_sys->i_port, i_status );
+
+ size_t i_resp_content_length = vlc_strtoi( vlc_dictionary_value_for_key( p_resp_headers, "Content-Length" ) );
+ if ( ppsz_resp_body != NULL && i_resp_content_length > 0 )
+ {
+ if ( *ppsz_resp_body != NULL )
+ {
+ free( *ppsz_resp_body );
+ *ppsz_resp_body = NULL;
+ }
+ *ppsz_resp_body = calloc( i_resp_content_length, sizeof( **ppsz_resp_body ) );
+ net_Read( p_this, i_fd, *ppsz_resp_body, i_resp_content_length );
+ } else if ( i_resp_content_length > 0 )
+ {
+ // read till end
+ size_t to_read = i_resp_content_length;
+ char dump[100];
+ while ( to_read > 0 )
+ {
+ to_read -= net_Read( p_this, i_fd, dump, to_read < 100 ? to_read : 100 );
+ }
+ }
+
+ if ( pi_resp_content_length != NULL )
+ {
+ *pi_resp_content_length = i_resp_content_length;
+ }
+
+ if ( pi_fd == NULL )
+ {
+ net_Close( i_fd );
+ }
+
+ if ( free_headers )
+ {
+ vlc_dictionary_clear( p_resp_headers, NULL, NULL );
+ free( p_resp_headers );
+ }
+
+ return i_status;
+}
+
+static int16_t ParseStatusCode( vlc_object_t *p_this, int i_fd )
+{
+ int64_t i_status = VLC_EGENERIC;
+ char *psz_working = NULL;
+ char *psz_token = NULL;
+
+ char *psz_status = net_Gets( p_this, i_fd );
+ if ( psz_status == NULL )
+ goto error;
+
+ psz_working = strdup( psz_status );
+ psz_token = strsep( &psz_working, " " );
+ if ( !psz_token || !( strncmp( psz_token, "HTTP", 4 ) == 0 || strncmp( psz_token, "RTSP", 4 ) == 0 ) )
+ {
+ msg_Err( p_this, "Unknown protocol (%s)", psz_status );
+ goto error;
+ }
+
+ psz_token = strsep( &psz_working, " " );
+ if ( !psz_token )
+ {
+ msg_Err( p_this, "Request failed (%s)", psz_status );
+ goto error;
+ }
+
+ i_status = vlc_strtoi( psz_token );
+ if ( i_status > 600 || i_status < -600 )
+ i_status = VLC_EGENERIC;
+
+ error:
+ if ( psz_status != NULL ) free( psz_status );
+ return (int16_t)i_status;
+}
+
+static int ParseHeaders( vlc_object_t *p_this, int i_fd, vlc_dictionary_t *headers )
+{
+ char *psz_line = NULL;
+ char *psz_line_copy = NULL;
+ char *psz_token;
+
+ char *psz_key;
+ char *psz_value;
+
+ if ( headers != NULL )
+ vlc_dictionary_init( headers, 0 );
+
+ psz_line = net_Gets( p_this, i_fd );
+ while ( psz_line != NULL && psz_line[0] != '\0' && strncmp( psz_line, "\r\n", 2 ) != 0 )
+ {
+ psz_line_copy = strdup( psz_line );
+ psz_token = strsep( &psz_line, ":" );
+
+ if ( psz_token == NULL || psz_line[0] != ' ' )
+ {
+ msg_Err( p_this, "Invalid header format (%s)", psz_line_copy );
+ return VLC_EGENERIC;
+ }
+
+ psz_key = psz_token;
+ psz_value = psz_line + 1;
+
+ if ( headers != NULL )
+ vlc_dictionary_insert( headers, psz_key, strdup( psz_value ) );
+
+ free( psz_line_copy );
+ psz_line = net_Gets( p_this, i_fd );
+ }
+
+ return VLC_SUCCESS;
+}
+
+#ifdef AIRPLAY_DIGEST
+/**
+ * Extracts the realm and nonce from a WWW-Authenticate header.
+ * @param psz_www_authenticate The value of a WWW-Authenticate header: Digest realm="airplay", nonce="MTUyNzAwODYxNCAPT9wiaibdj88Fccpz8aCD"
+ * @param ppsz_realm
+ * @param ppsz_nonce
+ */
+static int ParseWWWAuthenticate( char *psz_www_authenticate, char **ppsz_realm_value, char **ppsz_nonce_value )
+{
+ if ( strncmp( psz_www_authenticate, "Digest ", 7 ) != 0 ) return VLC_EGENERIC;
+
+ psz_www_authenticate += 7;
+ char *psz_nonce;
+ char *psz_realm = strtok_r( psz_www_authenticate, "; ", &psz_nonce );
+
+ *ppsz_nonce_value = calloc( sizeof( char ), strlen( psz_nonce ) );
+ *ppsz_realm_value = calloc( sizeof( char ), strlen( psz_realm ) );
+
+ sscanf( psz_nonce, "%*[^\"]\"%[^\"]\"", *ppsz_nonce_value );
+ sscanf( psz_realm, "%*[^\"]\"%[^\"]\"", *ppsz_realm_value );
+
+ return VLC_SUCCESS;
+}
+#endif
+
+/*****************************************************************************
+ * Utility
+ *****************************************************************************/
+
+static server_info_t *GetServerInfo( vlc_object_t *p_this )
+{
+ server_info_t *p_info = NULL;
+ vlc_dictionary_t headers;
+ char *psz_body = NULL;
+ size_t i_content_length;
+ int16_t status;
+
+ status = DoHttp( p_this, NULL, "GET", "/info", NULL, NULL, 0, NULL, &psz_body, &i_content_length, &headers );
+
+ if ( status != 200 )
+ {
+ msg_Err( p_this, "Unexpected response from Apple TV" );
+ return NULL;
+ }
+
+ plist_t p_plist = NULL;
+ if ( plist_is_binary( psz_body, i_content_length ) )
+ plist_from_bin( psz_body, i_content_length, &p_plist );
+ else
+ plist_from_xml( psz_body, i_content_length, &p_plist );
+
+ if ( p_plist == NULL )
+ {
+ msg_Err( p_this, "Server produced invalid response" );
+ return NULL;
+ }
+
+ p_info = calloc( 1, sizeof( *p_info ) );
+ plist_get_string_val( plist_dict_get_item( p_plist, "deviceID" ), &p_info->psz_deviceid );
+ plist_get_string_val( plist_dict_get_item( p_plist, "model" ), &p_info->psz_model );
+ plist_get_uint_val( plist_dict_get_item( p_plist, "features" ), &p_info->i_features );
+
+ vlc_dictionary_clear( &headers, NULL, NULL );
+ plist_free( p_plist );
+
+ free( psz_body );
+ return p_info;
+}
+
+static sout_stream_id_sys_t *GetSubId( const sout_stream_t *p_stream, const sout_stream_id_sys_t *id, bool update )
+{
+ sout_stream_sys_t *p_sys = p_stream->p_sys;
+
+ if ( update && UpdateOutput( p_stream ) == false )
+ {
+ return NULL;
+ }
+
+ for ( size_t i = 0; i < vlc_array_count( p_sys->p_streams_out ); ++i )
+ {
+ sout_stream_id_sys_t *stream_out_id = (sout_stream_id_sys_t *)vlc_array_item_at_index(
+ p_sys->p_streams_out, i );
+ if ( id == stream_out_id )
+ {
+ return stream_out_id->p_sub_id;
+ }
+ }
+ return NULL;
+}
+
+static bool CanDecodeAudio( vlc_fourcc_t codec )
+{
+ return codec == VLC_CODEC_MP3 ||
+ codec == VLC_CODEC_MP4A;
+}
+
+static bool CanDecodeVideo( vlc_fourcc_t codec )
+{
+ return codec == VLC_CODEC_H264;
+}
+
+static bool UpdateOutput( const sout_stream_t *p_stream )
+{
+ sout_stream_sys_t *p_sys = p_stream->p_sys;
+
+ if ( !p_sys->b_souts_changed )
+ {
+ return true;
+ }
+
+ p_sys->b_souts_changed = false;
+
+ bool b_supports_video =
+ ( p_sys->p_server_info->i_features & FEATURE_VIDEO ) > 0 &&
+ ( p_sys->p_server_info->i_features & FEATURE_VIDEO_HTTP_LIVE_STREAMS ) > 0;
+ bool b_supports_audio =
+ ( p_sys->p_server_info->i_features & FEATURE_AUDIO ) > 0;
+
+ bool canRemux = true;
+ vlc_fourcc_t i_codec_video = 0, i_codec_audio = 0;
+ const es_format_t *p_original_audio = NULL;
+ const es_format_t *p_original_video = NULL;
+ bool b_out_streams_changed = false;
+ vlc_array_t *new_streams = calloc( 1, sizeof( *new_streams ) );
+ vlc_array_init( new_streams );
+
+ sout_stream_id_sys_t *p_stream_id;
+ for ( size_t i = 0; i < vlc_array_count( p_sys->p_streams ); ++i )
+ {
+ p_stream_id = vlc_array_item_at_index( p_sys->p_streams, i );
+ const es_format_t *p_es = &p_stream_id->fmt;
+ if ( b_supports_audio && p_es->i_cat == AUDIO_ES && p_original_audio == NULL )
+ {
+ if ( !CanDecodeAudio( p_es->i_codec ) )
+ {
+ msg_Dbg( p_stream, "can't remux audio track %d codec %4.4s", p_es->i_id, (const char *)&p_es->i_codec );
+ canRemux = false;
+ } else if ( i_codec_audio == 0 )
+ {
+ i_codec_audio = p_es->i_codec;
+ }
+ p_original_audio = p_es;
+ vlc_array_append( new_streams, p_stream_id );
+ } else if ( b_supports_video && p_es->i_cat == VIDEO_ES )
+ {
+ if ( !CanDecodeVideo( p_es->i_codec ) )
+ {
+ msg_Dbg( p_stream, "can't remux video track %d codec %4.4s", p_es->i_id, (const char *)&p_es->i_codec );
+ canRemux = false;
+ } else if ( i_codec_video == 0 )
+ {
+ i_codec_video = p_es->i_codec;
+ }
+ p_original_video = p_es;
+ vlc_array_append( new_streams, p_stream_id );
+ } else
+ {
+ continue;
+ }
+
+ for ( size_t j = 0; j < vlc_array_count( p_sys->p_streams_out ) && !p_sys->b_out_force_reload; ++j )
+ {
+ if ( vlc_array_item_at_index( p_sys->p_streams_out, j ) == p_stream_id )
+ {
+ b_out_streams_changed = true;
+ break;
+ }
+ }
+ }
+
+ if ( vlc_array_count( new_streams ) == 0 )
+ {
+ StopPlayback( p_stream, false );
+ return true;
+ }
+
+ if ( !p_sys->b_out_force_reload && vlc_array_count( new_streams ) == vlc_array_count( p_sys->p_streams_out ) &&
+ !b_out_streams_changed )
+ {
+ return true;
+ }
+
+ p_sys->b_out_force_reload = false;
+
+ char *psz_sout_template = NULL;
+ if ( !canRemux )
+ {
+ if ( !p_sys->b_perf_warning_shown && i_codec_video == 0 && p_original_video
+ && var_InheritInteger( p_stream, SOUT_CFG_PREFIX
+ "show-perf-warning" ) )
+ {
+ int res = vlc_dialog_wait_question( p_stream,
+ VLC_DIALOG_QUESTION_WARNING,
+ _( "Cancel" ), _( "OK" ), _( "Ok, Don't warn me again" ),
+ _( "Performance warning" ),
+ _( "Casting this video requires conversion. "
+ "This conversion can use all the available power and "
+ "could quickly drain your battery." ) );
+ if ( res <= 0 )
+ return false;
+ p_sys->b_perf_warning_shown = true;
+ if ( res == 2 )
+ config_PutInt( SOUT_CFG_PREFIX"show-perf-warning", 0 );
+ }
+
+ bool b_transcode_video = i_codec_video == 0 && p_original_video;
+ bool b_transcode_audio = i_codec_audio == 0 && p_original_audio;
+ psz_sout_template = "transcode{%s%s}:http{dst=%s,mux=ts}";
+ char *psz_tmp = NULL;
+
+ asprintf( &psz_tmp, psz_sout_template, b_transcode_audio ? "acodec=mp3 ," : "" );
+ free( psz_sout_template );
+ psz_sout_template = psz_tmp;
+ psz_tmp = NULL;
+
+ asprintf( &psz_tmp, psz_sout_template, b_transcode_video ? "vcodec=h264,maxwidth=1920,maxheight=1080," : "" );
+ free( psz_sout_template );
+ psz_sout_template = psz_tmp;
+ psz_tmp = NULL;
+ } else
+ {
+ psz_sout_template = "http{dst=%s,mux=ts}";
+ }
+
+ int err = startSoutChain( (vlc_object_t *)p_stream, new_streams, psz_sout_template );
+ if ( err != VLC_SUCCESS )
+ {
+ StopPlayback( p_stream, false );
+ return false;
+ }
+ return true;
+}
+
+static void DelInternal( sout_stream_t *p_stream, void *_id, bool b_reset_config )
+{
+ sout_stream_id_sys_t *id = (sout_stream_id_sys_t *)_id;
+ sout_stream_sys_t *p_sys = (sout_stream_sys_t *)p_stream->p_sys;
+
+ size_t i_streams_handled = 0;
+ sout_stream_id_sys_t *p_sys_id = NULL;
+ while ( i_streams_handled < vlc_array_count( p_sys->p_streams ) )
+ {
+ p_sys_id = vlc_array_item_at_index( p_sys->p_streams, i_streams_handled );
+ if ( p_sys_id == id )
+ {
+ if ( p_sys_id->p_sub_id != NULL )
+ {
+ sout_StreamIdDel( p_sys->p_out, p_sys_id->p_sub_id );
+
+ size_t i_streams_out_handled = 0;
+ sout_stream_id_sys_t *p_sys_out_id = NULL;
+ while ( i_streams_out_handled < vlc_array_count( p_sys->p_streams_out ) )
+ {
+ p_sys_out_id = vlc_array_item_at_index( p_sys->p_streams_out, i_streams_out_handled );
+ if ( p_sys_out_id == id )
+ {
+ vlc_array_remove( p_sys->p_streams_out, i_streams_out_handled );
+ p_sys->b_souts_changed = b_reset_config;
+ p_sys->b_out_force_reload = b_reset_config;
+ if ( p_sys_id->fmt.i_cat == VIDEO_ES )
+ p_sys->b_has_video = false;
+ break;
+ } else
+ {
+ i_streams_out_handled++;
+ }
+ }
+ }
+
+ es_format_Clean( &p_sys_id->fmt );
+ free( p_sys_id );
+ vlc_array_remove( p_sys->p_streams, i_streams_handled );
+ break;
+ } else
+ {
+ i_streams_handled++;
+ }
+ }
+
+ if ( vlc_array_count( p_sys->p_streams ) == 0 )
+ StopPlayback( p_stream, false );
+}
+
+static bool IsFlushing( sout_stream_t *p_stream )
+{
+ sout_stream_sys_t *p_sys = p_stream->p_sys;
+ if ( p_sys->b_airplay_flushing )
+ {
+ return false;
+ }
+
+ for ( size_t i = 0; i < vlc_array_count( p_sys->p_streams_out ); i++ )
+ {
+ const sout_stream_id_sys_t *p_stream_id = vlc_array_item_at_index( p_sys->p_streams_out, i );
+ if ( p_stream_id->flushed )
+ return true;
+ }
+
+ p_sys->b_airplay_flushing = false;
+
+ for ( size_t i = 0; i < vlc_array_count( p_sys->p_streams_out ); i++ )
+ {
+ sout_stream_id_sys_t *p_stream_id = vlc_array_item_at_index( p_sys->p_streams_out, i );
+ p_stream_id->flushed = false;
+ }
+
+ return false;
+}
+
+static int startSoutChain( vlc_object_t *p_this, vlc_array_t *new_streams, const char *psz_sout_template )
+{
+ sout_stream_t *p_stream = (sout_stream_t *)p_this;
+ sout_stream_sys_t *p_sys = p_stream->p_sys;
+
+ stopSoutChain( p_stream );
+ p_sys->b_has_video = false;
+ p_sys->p_streams_out = new_streams;
+
+ uint16_t i_stream_port = 0;
+ uint16_t i_playlist_port = 0;
+ do
+ {
+ i_stream_port = (uint16_t)( rand() % 65536 );
+ } while ( i_stream_port < 10000 || !PortIsOpen( p_this, p_sys->psz_ipv4_ip, i_stream_port ) );
+
+ do
+ {
+ i_playlist_port = (uint16_t)( rand() % 65536 );
+ } while ( i_playlist_port < 10000 || !PortIsOpen( p_this, p_sys->psz_ipv4_ip, i_playlist_port ) );
+
+ vlc_url_t uri = {0};
+ uri.psz_protocol = "http";
+ uri.psz_host = p_sys->psz_ipv4_ip;
+ uri.i_port = i_stream_port;
+ uri.psz_path = "/out.ts";
+
+ char *psz_uri = vlc_uri_compose( &uri );
+ char *psz_sout = NULL;
+ asprintf( &psz_sout, psz_sout_template,
+ psz_uri + strlen( "http://" ) ); // we don't want the 'http://' protocol prefix
+ msg_Info( p_this, "creating chain %s", psz_sout );
+
+ p_sys->p_out = sout_StreamChainNew( p_stream->p_sout, psz_sout, NULL, NULL );
+ if ( p_sys->p_out == NULL )
+ {
+ msg_Dbg( p_this, "could not create sout chain:%s", psz_sout );
+ vlc_array_clear( p_sys->p_streams_out );
+ free( psz_sout );
+ free( psz_uri );
+ return VLC_EGENERIC;
+ }
+
+ var_Create( p_stream, "http-port", VLC_VAR_INTEGER );
+ var_SetInteger( p_stream, "http-port", i_playlist_port );
+ var_Create( p_stream, "http-host", VLC_VAR_STRING );
+ var_SetString( p_stream, "http-host", p_sys->psz_ipv4_ip );
+ p_sys->p_httpd_host = vlc_http_HostNew( VLC_OBJECT( p_stream ) );
+ if ( p_sys->p_httpd_host == NULL )
+ {
+ msg_Err( p_this, "could not open http server. It's likely the port wasn't closed after checking." );
+ return VLC_EGENERIC;
+ }
+
+ char *psz_m3u8 = NULL;
+ asprintf( &psz_m3u8,
+ "#EXTM3U\n"
+ "\n"
+ "#EXT-X-VERSION:3\n"
+ "\n"
+ "\n"
+ "\n"
+ "#EXTINF:0,\n"
+ "\n"
+ "%s",
+ psz_uri );
+
+ p_sys->psz_m3u8 = psz_m3u8;
+ uri.psz_path = "/playlist.m3u8";
+ p_sys->p_m3u8_file = httpd_FileNew( p_sys->p_httpd_host, uri.psz_path, "application/x-mpegURL", NULL, NULL,
+ (httpd_file_callback_t)m3u8Callback, (void *)p_sys );
+ if ( p_sys->p_m3u8_file == NULL )
+ {
+ msg_Err( p_this, "could not add playlist to server." );
+ httpd_HostDelete( p_sys->p_httpd_host );
+ p_sys->p_httpd_host = NULL;
+ return VLC_EGENERIC;
+ }
+
+ uri.i_port = i_playlist_port;
+ psz_uri = vlc_uri_compose( &uri );
+ msg_Info( p_this, "serving playlist on %s", psz_uri );
+
+ size_t i_streams_handled = 0;
+ sout_stream_id_sys_t *p_stream_id;
+ while ( i_streams_handled < vlc_array_count( p_sys->p_streams_out ) )
+ {
+ p_stream_id = vlc_array_item_at_index( p_sys->p_streams_out, i_streams_handled );
+ p_stream_id->p_sub_id = sout_StreamIdAdd( p_sys->p_out, &p_stream_id->fmt );
+ if ( p_stream_id->p_sub_id == NULL )
+ {
+ msg_Err( p_stream, "can't handle %4.4s stream", (char *)&p_stream_id->fmt.i_codec );
+ es_format_Clean( &p_stream_id->fmt );
+ vlc_array_remove( p_sys->p_streams_out, i_streams_handled );
+ } else
+ {
+ if ( p_stream_id->fmt.i_cat == VIDEO_ES )
+ {
+ p_sys->b_has_video = true;
+ }
+ i_streams_handled += 1;
+ }
+ }
+
+ if ( vlc_array_count( p_sys->p_streams_out ) == 0 )
+ {
+ stopSoutChain( p_stream );
+ return VLC_EGENERIC;
+ }
+
+ return StartPlayback( p_this, psz_uri );
+}
+
+static void stopSoutChain( sout_stream_t *p_stream )
+{
+ sout_stream_sys_t *p_sys = p_stream->p_sys;
+ if ( p_sys->p_m3u8_file != NULL )
+ {
+ httpd_FileDelete( p_sys->p_m3u8_file );
+ p_sys->p_m3u8_file = NULL;
+ }
+
+ if ( p_sys->p_httpd_host != NULL )
+ {
+ httpd_HostDelete( p_sys->p_httpd_host );
+ p_sys->p_httpd_host = NULL;
+ }
+
+ if ( unlikely( p_sys->p_out != NULL ) )
+ {
+ sout_stream_id_sys_t *p_stream_id;
+ for ( size_t i = 0; i < vlc_array_count( p_sys->p_streams_out ); i++ )
+ {
+ p_stream_id = vlc_array_item_at_index( p_sys->p_streams_out, i );
+ if ( p_stream_id->p_sub_id != NULL )
+ {
+ sout_StreamIdDel( p_sys->p_out, p_stream_id->p_sub_id );
+ p_stream_id->p_sub_id = NULL;
+ }
+ }
+ vlc_array_clear( p_sys->p_streams_out );
+ sout_StreamChainDelete( p_sys->p_out, NULL );
+ p_sys->p_out = NULL;
+ }
+}
+
+static int StartPlayback( vlc_object_t *p_this, const char *psz_uri )
+{
+ sout_stream_t *p_stream = (sout_stream_t *)p_this;
+ sout_stream_sys_t *p_sys = (sout_stream_sys_t *)p_stream->p_sys;
+
+ if ( p_sys->p_ping_ctx != NULL || p_sys->p_ping_thread != NULL )
+ {
+ assert( p_sys->p_ping_ctx != NULL && p_sys->p_ping_thread != NULL );
+ StopPlayback( p_this, false );
+ assert( p_sys->p_ping_ctx == NULL && p_sys->p_ping_thread == NULL );
+ }
+
+ vlc_dictionary_t keep_alive_headers;
+ vlc_dictionary_init( &keep_alive_headers, 1 );
+ vlc_dictionary_insert( &keep_alive_headers, "Connection", "Keep-Alive" );
+
+ // play
+ uint8_t *p_binary_plist = NULL;
+ uint32_t i_content_length;
+ plist_t p_plist = plist_new_dict();
+ plist_dict_set_item( p_plist, "Content-Location", plist_new_string( psz_uri ) );
+ plist_to_bin( p_plist, (char **)&p_binary_plist, &i_content_length );
+
+ int16_t i_status = DoHttp( p_this, &p_sys->i_authenticated_fd, "POST", "/play", "application/x-apple-binary-plist",
+ (const char *)p_binary_plist, i_content_length, &keep_alive_headers, NULL, NULL, NULL );
+
+ if ( i_status != 200 )
+ {
+ msg_Err( p_this, "Could not start playback, status code: %d", i_status );
+ return VLC_EGENERIC;
+ }
+
+ vlc_dictionary_clear( &keep_alive_headers, NULL, NULL );
+
+ // init thread context
+ p_sys->p_ping_ctx = calloc( 1, sizeof( *p_sys->p_ping_ctx ) );
+ p_sys->p_ping_ctx->i_authenticated_fd = p_sys->i_authenticated_fd;
+ p_sys->p_ping_ctx->p_module = p_this;
+ vlc_mutex_init( &p_sys->p_ping_ctx->lock );
+
+ // init thread and clone
+ p_sys->p_ping_thread = calloc( 1, sizeof( *p_sys->p_ping_thread ) );
+ return vlc_clone( p_sys->p_ping_thread, PingThread, p_sys->p_ping_ctx, 1 );
+}
+
+static int StopPlayback( vlc_object_t *p_this, bool close_connection )
+{
+ sout_stream_t *p_stream = (sout_stream_t *)p_this;
+ sout_stream_sys_t *p_sys = (sout_stream_sys_t *)p_stream->p_sys;
+
+ if ( p_sys->p_ping_ctx == NULL || &p_sys->p_ping_thread == NULL ) return VLC_EGENERIC;
+
+ vlc_mutex_lock( &p_sys->p_ping_ctx->lock );
+ p_sys->p_ping_ctx->b_join = true;
+ vlc_mutex_unlock( &p_sys->p_ping_ctx->lock );
+ vlc_join( *p_sys->p_ping_thread, NULL );
+
+ vlc_dictionary_t keep_alive_headers;
+ vlc_dictionary_init( &keep_alive_headers, 1 );
+ vlc_dictionary_insert( &keep_alive_headers, "Connection", "Keep-Alive" );
+
+ vlc_mutex_lock( &p_sys->p_ping_ctx->lock );
+ DoHttp( p_this, &p_sys->p_ping_ctx->i_authenticated_fd, "POST", "/stop",
+ NULL, NULL, 0, &keep_alive_headers, NULL, NULL, NULL );
+ vlc_mutex_unlock( &p_sys->p_ping_ctx->lock );
+
+ free( p_sys->p_ping_thread );
+ p_sys->p_ping_thread = NULL;
+ vlc_mutex_destroy( &p_sys->p_ping_ctx->lock );
+ free( p_sys->p_ping_ctx );
+ p_sys->p_ping_ctx = NULL;
+
+ if ( close_connection )
+ {
+ msg_Dbg( p_this, "no more outputs, closing connection" );
+ net_Close( p_sys->i_authenticated_fd );
+ p_sys->i_authenticated_fd = 0;
+ }
+
+ return VLC_SUCCESS;
+}
+
+static void *PingThread( void *obj )
+{
+ ping_ctx_t *ctx = (ping_ctx_t *)obj;
+ uint64_t counter = 0;
+
+ while ( true )
+ {
+ vlc_mutex_lock( &ctx->lock );
+ if ( ctx->b_join )
+ {
+ vlc_mutex_unlock( &ctx->lock );
+ break;
+ }
+
+ if ( counter % 300 == 0 )
+ {
+ DoHttp( ctx->p_module, &ctx->i_authenticated_fd, "GET", "/info", NULL, NULL, 0, NULL, NULL, NULL, NULL );
+ }
+
+ vlc_mutex_unlock( &ctx->lock );
+ vlc_tick_sleep( 100000 );
+ counter += 1;
+ }
+
+ return (void *)VLC_SUCCESS;
+}
+
+static int m3u8Callback( httpd_file_sys_t *p_args,
+ httpd_file_t *f, uint8_t const *p_request,
+ uint8_t **pp_data, int *pi_data )
+{
+ VLC_UNUSED( f );
+ VLC_UNUSED( p_request );
+ sout_stream_sys_t *p_sys = (sout_stream_sys_t *)p_args;
+
+ vlc_mutex_lock( &p_sys->lock_m3u8 );
+ if ( p_sys->psz_m3u8 != NULL )
+ {
+ *pi_data = strlen( p_sys->psz_m3u8 );
+ *pp_data = malloc( sizeof( **pp_data ) * *pi_data );
+ memcpy( *pp_data, p_sys->psz_m3u8, *pi_data );
+ } else
+ {
+ *pp_data = NULL;
+ *pi_data = 0;
+ }
+ vlc_mutex_unlock( &p_sys->lock_m3u8 );
+
+ return VLC_SUCCESS;
+}
+
+static inline bool HasFeature( server_info_t *p_info, uint64_t i_feature )
+{
+ return p_info == NULL ? false : ( p_info->i_features & i_feature ) > 0;
+}
+
+static void FreeServerInfo( server_info_t **pp_info )
+{
+ if ( pp_info == NULL ) return;
+ free( ( *pp_info )->psz_deviceid );
+ free( ( *pp_info )->psz_model );
+ free( *pp_info );
+ *pp_info = NULL;
+}
+
+static void FreeSys( sout_stream_sys_t *p_sys )
+{
+ if ( p_sys == NULL ) return;
+ if ( p_sys->psz_bonjour_hostname != NULL ) free( p_sys->psz_bonjour_hostname );
+ if ( p_sys->psz_ipv4_ip != NULL ) free( p_sys->psz_ipv4_ip );
+ if ( p_sys->p_sexp_eddsa_keypair != NULL ) gcry_sexp_release( *p_sys->p_sexp_eddsa_keypair );
+ if ( p_sys->p_keystore != NULL ) vlc_keystore_release( p_sys->p_keystore );
+ if ( p_sys->p_server_info != NULL ) FreeServerInfo( &p_sys->p_server_info );
+ if ( p_sys->psz_m3u8 != NULL ) free( p_sys->psz_m3u8 );
+ if ( p_sys->p_m3u8_file != NULL ) httpd_FileDelete( p_sys->p_m3u8_file );
+ if ( p_sys->p_httpd_host != NULL ) httpd_HostDelete( p_sys->p_httpd_host );
+ if ( p_sys->p_streams_out != NULL ) vlc_array_clear( p_sys->p_streams_out );
+ if ( p_sys->p_streams != NULL ) vlc_array_clear( p_sys->p_streams );
+}
+
+static int64_t vlc_strtoi( const char *str )
+{
+ char *end;
+ long long l;
+
+ errno = 0;
+ l = strtoll( str, &end, 0 );
+
+ if ( !errno )
+ {
+#if ( LLONG_MAX > 0x7fffffffffffffffLL )
+ if (l > 0x7fffffffffffffffLL
+ || l < -0x8000000000000000LL)
+ errno = ERANGE;
+#endif
+ if ( *end )
+ errno = EINVAL;
+ }
+ return l;
+}
+
+static void ReverseBuffer( uint8_t *buffer, size_t length )
+{
+ uint8_t tmp;
+ for ( size_t i = 0; i < length / 2; i++ )
+ {
+ tmp = buffer[i];
+ buffer[i] = buffer[length - 1 - i];
+ buffer[length - 1 - i] = tmp;
+ }
+}
+
+static bool PortIsOpen( vlc_object_t *p_this, char *psz_host, uint16_t i_port )
+{
+ int *p_fd = net_ListenTCP( p_this, psz_host, i_port );
+ if ( p_fd == NULL )
+ {
+ return false;
+ } else
+ {
+ net_Close( *p_fd );
+ free( p_fd );
+ return true;
+ }
+}
+
+/*****************************************************************************
+ * Cryptography
+ *****************************************************************************/
+
+static int GetOrCreateEd25519( vlc_object_t *p_this, gcry_sexp_t **pp_sexp_ed )
+{
+ gcry_error_t gcry_error = 0;
+
+ uint8_t *p_bytes_ed_pub = NULL;
+ size_t i_length_ed_pub = 0;
+
+ uint8_t *p_bytes_ed_priv = NULL;
+ size_t i_length_ed_priv = 0;
+
+#if 0
+ HexToBytes( HARDCODED_ED_PUBLIC, &p_bytes_ed_pub, &i_length_ed_pub );
+ HexToBytes( HARDCODED_ED_PRIVATE, &p_bytes_ed_priv, &i_length_ed_priv );
+#else
+ sout_stream_t *p_stream = (sout_stream_t *)p_this;
+ sout_stream_sys_t *p_sys = p_stream->p_sys;
+
+ vlc_keystore_entry *entries;
+ char *ppsz_values[KEY_MAX];
+ VLC_KEYSTORE_VALUES_INIT( ppsz_values );
+ ppsz_values[KEY_PROTOCOL] = AIRPLAY_USERNAME;
+ ppsz_values[KEY_SERVER] = p_sys->p_server_info->psz_deviceid;
+ unsigned int entries_count = vlc_keystore_find( p_sys->p_keystore, ppsz_values, &entries );
+
+ if ( entries_count < 1 )
+ {
+ gcry_sexp_t sexp_genkey_params = NULL;
+ gcry_sexp_t p_eddsa_keypair = NULL;
+
+ // need to generate key and store it
+ msg_Dbg( p_this, "Could not find Airplay credentials, creating now." );
+
+ if ( ( gcry_error = gcry_sexp_build( &sexp_genkey_params, NULL,
+ "(genkey (ecdsa (curve \"Ed25519\")"
+ "(flags eddsa)))" ) ) )
+ {
+ msg_Err( p_this, "eddsa params failed: %s/%s", gcry_strsource( gcry_error ), gcry_strerror( gcry_error ) );
+ return VLC_EGENERIC;
+ }
+
+ if ( ( gcry_error = gcry_pk_genkey( &p_eddsa_keypair, sexp_genkey_params ) ) )
+ {
+ msg_Err( p_this, "eddsa keygen failed: %s/%s", gcry_strsource( gcry_error ), gcry_strerror( gcry_error ) );
+ return VLC_EGENERIC;
+ }
+
+ gcry_sexp_release( sexp_genkey_params );
+
+ gcry_mpi_t mpi_ed_pub;
+ gcry_mpi_t mpi_ed_priv;
+ gcry_error = gcry_sexp_extract_param( p_eddsa_keypair, NULL, "qd", &mpi_ed_pub,
+ &mpi_ed_priv, NULL );
+
+ if ( gcry_error )
+ {
+ msg_Err( p_this, "param extract failed: %s/%s", gcry_strsource( gcry_error ), gcry_strerror( gcry_error ) );
+ return VLC_EGENERIC;
+ }
+
+ ppsz_values[KEY_USER] = "USER";
+
+ size_t i_length_ed_pub_priv = 64;
+ uint8_t *p_bytes_ed_pub_priv = malloc( i_length_ed_pub_priv * sizeof( *p_bytes_ed_pub_priv ) );
+ gcry_error = gcry_mpi_print( GCRYMPI_FMT_USG, p_bytes_ed_pub_priv, i_length_ed_pub_priv / 2, NULL, mpi_ed_pub );
+ gcry_error = gcry_mpi_print( GCRYMPI_FMT_USG, p_bytes_ed_pub_priv + i_length_ed_pub_priv / 2,
+ i_length_ed_pub_priv / 2, NULL, mpi_ed_priv );
+ char *psz_b64_ed_pub_priv = vlc_b64_encode_binary( p_bytes_ed_pub_priv, i_length_ed_pub_priv );
+
+ vlc_keystore_store( p_sys->p_keystore, ppsz_values, (const uint8_t *)psz_b64_ed_pub_priv, -1,
+ "Airplay Credentials" );
+ entries_count = vlc_keystore_find( p_sys->p_keystore, ppsz_values, &entries );
+
+ free( p_bytes_ed_pub_priv );
+ free( psz_b64_ed_pub_priv );
+ }
+
+ vlc_keystore_entry entry = entries[0];
+
+ // if user or secret are unexpected, regenerate
+ if ( entry.ppsz_values[KEY_USER] == NULL ||
+ entry.i_secret_len == 0 )
+ {
+ msg_Err( p_this, "Invalid Configuration, Regenerating" );
+ vlc_keystore_remove( p_sys->p_keystore, ppsz_values );
+ return GetOrCreateEd25519( p_this, pp_sexp_ed );
+ }
+
+ uint8_t *p_bytes_ed_pub_priv = NULL;
+ size_t i_length_ed_pub_priv = vlc_b64_decode_binary( &p_bytes_ed_pub_priv, (const char *)entry.p_secret );
+ i_length_ed_pub = i_length_ed_pub_priv / 2;
+ i_length_ed_priv = i_length_ed_pub_priv / 2;
+ p_bytes_ed_pub = malloc( i_length_ed_pub );
+ p_bytes_ed_priv = malloc( i_length_ed_priv );
+ memcpy( p_bytes_ed_pub, p_bytes_ed_pub_priv, i_length_ed_pub_priv / 2 );
+ memcpy( p_bytes_ed_priv, p_bytes_ed_pub_priv + i_length_ed_pub_priv / 2, i_length_ed_pub_priv / 2 );
+ free( p_bytes_ed_pub_priv );
+#endif
+
+ gcry_sexp_t *p_eddsa_keypair = calloc( 1, sizeof( p_eddsa_keypair ) );
+
+ gcry_error = gcry_sexp_build( p_eddsa_keypair, NULL,
+ "("
+ " key-data"
+ " (public-key"
+ " (ecc"
+ " (curve \"Ed25519\")"
+ " (flags eddsa)"
+ " (q%b)"
+ " )"
+ " )"
+ " (private-key"
+ " (ecc"
+ " (curve \"Ed25519\")"
+ " (flags eddsa)"
+ " (q%b)"
+ " (d%b)"
+ " )"
+ " )"
+ ")", i_length_ed_pub, p_bytes_ed_pub, i_length_ed_pub, p_bytes_ed_pub,
+ i_length_ed_priv, p_bytes_ed_priv );
+
+ if ( gcry_error )
+ {
+ msg_Err( p_this, "Ed25519 key build failed: %s/%s", gcry_strsource( gcry_error ), gcry_strerror( gcry_error ) );
+ return VLC_EGENERIC;
+ }
+
+ free( p_bytes_ed_priv );
+ free( p_bytes_ed_pub );
+
+ *pp_sexp_ed = p_eddsa_keypair;
+ return VLC_SUCCESS;
+}
+
+static int CreateCurve25519( vlc_object_t *p_this, gcry_sexp_t *p_sexp_curve )
+{
+ gcry_error_t gcry_error;
+
+#if 0
+ uint8_t *p_bytes_curve_public = NULL;
+ size_t i_length_curve_public;
+ HexToBytes( HARDCODED_CURVE_PUBLIC, &p_bytes_curve_public, &i_length_curve_public );
+
+ uint8_t *p_bytes_curve_private = NULL;
+ size_t i_length_curve_private;
+ HexToBytes( HARDCODED_CURVE_PRIVATE, &p_bytes_curve_private, &i_length_curve_private );
+
+ gcry_error = gcry_sexp_build( p_sexp_curve, NULL,
+ "("
+ " key-data"
+ " (public-key"
+ " (ecc"
+ " (curve \"Curve25519\")"
+ " (flags djb-tweak)"
+ " (q%b)"
+ " )"
+ " )"
+ " (private-key"
+ " (ecc"
+ " (curve \"Curve25519\")"
+ " (flags djb-tweak)"
+ " (q%b)"
+ " (d%b)"
+ " )"
+ " )"
+ ")",
+ i_length_curve_public, p_bytes_curve_public,
+ i_length_curve_public, p_bytes_curve_public,
+ i_length_curve_private, p_bytes_curve_private );
+
+ if ( gcry_error )
+ {
+ msg_Err( p_this, "Curve25519 key build failed: %s/%s", gcry_strsource( gcry_error ),
+ gcry_strerror( gcry_error ) );
+ return VLC_EGENERIC;
+ }
+
+ free( p_bytes_curve_public );
+ free( p_bytes_curve_private );
+#else
+ gcry_sexp_t sexp_genkey_params;
+
+ if ( ( gcry_error = gcry_sexp_build( &sexp_genkey_params, NULL,
+ "(genkey"
+ " (ecc"
+ " (curve \"Curve25519\")"
+ " (flags djb-tweak)"
+ " )"
+ ")" ) ) )
+ {
+ msg_Err( p_this, "curve params failed: %s/%s", gcry_strsource( gcry_error ), gcry_strerror( gcry_error ) );
+ return VLC_EGENERIC;
+ }
+
+ if ( ( gcry_error = gcry_pk_genkey( p_sexp_curve, sexp_genkey_params ) ) )
+ {
+ msg_Err( p_this, "curve generation failed: %s/%s", gcry_strsource( gcry_error ),
+ gcry_strerror( gcry_error ) );
+ return VLC_EGENERIC;
+ }
+
+ gcry_sexp_release( sexp_genkey_params );
+#endif
+ return VLC_SUCCESS;
+}
+
+static void EncryptEd25519( const struct srp_user *p_user, uint8_t *p_bytes_ed25519, size_t i_length_ed25519,
+ uint8_t **p_bytes_epk, uint8_t **p_bytes_authTag )
+{
+
+ uint8_t *p_bytes_aes_key;
+ uint8_t *p_bytes_aes_IV;
+ size_t i_length_aes_key_IV;
+ const uint8_t i_length_authTag = 16; // length is 16 for GCM mode
+
+ CalculatePairAESKeyIV( p_user, &p_bytes_aes_key, &p_bytes_aes_IV, &i_length_aes_key_IV );
+
+ *p_bytes_epk = calloc( i_length_ed25519, sizeof( **p_bytes_epk ) );
+ *p_bytes_authTag = calloc( i_length_authTag, sizeof( **p_bytes_authTag ) );
+
+ gcry_cipher_hd_t aes_handle;
+ gcry_cipher_open( &aes_handle, GCRY_CIPHER_AES128, GCRY_CIPHER_MODE_GCM, 0 );
+ gcry_cipher_setkey( aes_handle, p_bytes_aes_key, i_length_aes_key_IV );
+ gcry_cipher_setiv( aes_handle, p_bytes_aes_IV, i_length_aes_key_IV );
+
+ gcry_cipher_encrypt( aes_handle, *p_bytes_epk, i_length_ed25519, p_bytes_ed25519, i_length_ed25519 );
+ gcry_cipher_gettag( aes_handle, *p_bytes_authTag, i_length_authTag );
+
+ gcry_cipher_close( aes_handle );
+}
+
+static void CalculatePairAESKeyIV( const struct srp_user *p_user, uint8_t **p_bytes_aes_key,
+ uint8_t **p_bytes_aes_IV, size_t *p_i_length_aes_key_IV )
+{
+ size_t i_length_K;
+ uint8_t *p_bytes_K;
+ *p_i_length_aes_key_IV = 16;
+ srp_user_get_hashed_session_key( p_user, &p_bytes_K, &i_length_K );
+
+ ( *p_bytes_aes_key ) = calloc( *p_i_length_aes_key_IV, sizeof( *( *p_bytes_aes_key ) ) );
+ ( *p_bytes_aes_IV ) = calloc( *p_i_length_aes_key_IV, sizeof( *( *p_bytes_aes_IV ) ) );
+
+ gcry_md_hd_t digest_handle_key;
+ gcry_md_open( &digest_handle_key, GCRY_MD_SHA512, 0 );
+ gcry_md_write( digest_handle_key, "Pair-Setup-AES-Key", sizeof( "Pair-Setup-AES-Key" ) - 1 );
+ gcry_md_write( digest_handle_key, p_bytes_K, i_length_K );
+ memcpy( ( *p_bytes_aes_key ), gcry_md_read( digest_handle_key, GCRY_MD_SHA512 ), *p_i_length_aes_key_IV );
+ gcry_md_close( digest_handle_key );
+
+ gcry_md_hd_t digest_handle_IV;
+ gcry_md_open( &digest_handle_IV, GCRY_MD_SHA512, 0 );
+ gcry_md_write( digest_handle_IV, "Pair-Setup-AES-IV", sizeof( "Pair-Setup-AES-IV" ) - 1 );
+ gcry_md_write( digest_handle_IV, p_bytes_K, i_length_K );
+ memcpy( ( *p_bytes_aes_IV ), gcry_md_read( digest_handle_IV, GCRY_MD_SHA512 ), *p_i_length_aes_key_IV );
+ ( *p_bytes_aes_IV )[*p_i_length_aes_key_IV - 1] += 1; // last byte incremented by 1
+ gcry_md_close( digest_handle_IV );
+ free( p_bytes_K );
+}
+
+static void EdSignConcatenatedBytes( gcry_sexp_t sexp_ed_keypair,
+ const uint8_t *p_bytes_a, const size_t i_length_a,
+ const uint8_t *p_bytes_b, const size_t i_length_b,
+ uint8_t **p_bytes_ed_signature, size_t *i_length_ed_signature )
+{
+ gcry_error_t gcry_error;
+
+ uint8_t *p_bytes_concat = calloc( i_length_a + i_length_b, sizeof( *p_bytes_concat ) );
+ memcpy( p_bytes_concat, p_bytes_a, i_length_a );
+ memcpy( p_bytes_concat + i_length_a, p_bytes_b, i_length_b );
+
+ gcry_sexp_t sexp_message = NULL;
+ if ( ( gcry_error = gcry_sexp_build( &sexp_message, NULL,
+ "(data"
+ " (flags eddsa)"
+ " (hash-algo sha512)"
+ " (value %b))",
+ i_length_a + i_length_b, p_bytes_concat ) ) )
+ {
+ // msg_Err( p_this, "data sexp generation failed: %s/%s", gcry_strsource( gcry_error ), gcry_strerror( gcry_error ) );
+ }
+
+ free( p_bytes_concat );
+
+ gcry_sexp_t pub_key = gcry_sexp_find_token( sexp_ed_keypair, "public-key", 0 );
+ gcry_sexp_t sec_key = gcry_sexp_find_token( sexp_ed_keypair, "private-key", 0 );
+
+ gcry_sexp_t sexp_signature = NULL;
+ if ( ( gcry_error = gcry_pk_sign( &sexp_signature, sexp_message, sec_key ) ) )
+ {
+ // msg_Err( p_this, "data sexp generation failed: %s/%s", gcry_strsource( gcry_error ), gcry_strerror( gcry_error ) );
+ }
+
+ if ( ( gcry_error = gcry_pk_verify( sexp_signature, sexp_message, pub_key ) ) )
+ {
+ // msg_Err( p_this, "data sexp generation failed: %s/%s", gcry_strsource( gcry_error ), gcry_strerror( gcry_error ) );
+ }
+
+ gcry_sexp_release( sexp_message );
+ gcry_sexp_release( pub_key );
+ gcry_sexp_release( sec_key );
+
+ gcry_mpi_t mpi_r = NULL;
+ gcry_mpi_t mpi_s = NULL;
+ if ( ( gcry_error = gcry_sexp_extract_param( sexp_signature, NULL, "rs", &mpi_r, &mpi_s, NULL ) ) )
+ {
+ // msg_Err( p_this, "data sexp generation failed: %s/%s", gcry_strsource( gcry_error ), gcry_strerror( gcry_error ) );
+ }
+
+ gcry_sexp_release( sexp_signature );
+
+ uint8_t *p_bytes_r = NULL;
+ size_t i_length_r;
+ gcry_mpi_aprint( GCRYMPI_FMT_USG, &p_bytes_r, &i_length_r, mpi_r );
+
+ uint8_t *p_bytes_s = NULL;
+ size_t i_length_s;
+ gcry_mpi_aprint( GCRYMPI_FMT_USG, &p_bytes_s, &i_length_s, mpi_s );
+
+ *i_length_ed_signature = i_length_r + i_length_s;
+ *p_bytes_ed_signature = calloc( *i_length_ed_signature, sizeof( **p_bytes_ed_signature ) );
+
+ gcry_mpi_release( mpi_r );
+ gcry_mpi_release( mpi_s );
+
+ // R ~ S
+ memcpy( *p_bytes_ed_signature, p_bytes_r, i_length_r );
+ memcpy( *p_bytes_ed_signature + i_length_r, p_bytes_s, i_length_s );
+}
+
+static gcry_error_t Curve25519DiffieHellman( const uint8_t *p_bytes_a, size_t i_length_a, const uint8_t *p_bytes_B,
+ size_t i_length_B, uint8_t **pp_bytes_shared, size_t *p_i_length_shared )
+{
+ gcry_error_t gcry_error = 0;
+ uint8_t *p_bytes_a_reversed = calloc( i_length_a, sizeof( *p_bytes_a_reversed ) );
+ gcry_sexp_t sexp_a = NULL;
+ gcry_sexp_t sexp_B = NULL;
+ gcry_sexp_t sexp_shared_key = NULL;
+
+ gcry_mpi_t mpi_curve_shared_compressed = NULL;
+ gcry_mpi_t mpi_curve_shared = gcry_mpi_new( 0 );
+
+ memcpy( p_bytes_a_reversed, p_bytes_a, i_length_a );
+ ReverseBuffer( p_bytes_a_reversed, i_length_a );
+
+ if (
+ ( gcry_error = gcry_sexp_build( &sexp_a, NULL, "%b", i_length_a, p_bytes_a_reversed ) ) ||
+ ( gcry_error = gcry_sexp_build( &sexp_B, NULL,
+ "(public-key"
+ " (ecc"
+ " (curve \"Curve25519\")"
+ " (flags djb-tweak)"
+ " (q%b)))", i_length_B, p_bytes_B ) ) ||
+ ( gcry_error = gcry_pk_encrypt( &sexp_shared_key, sexp_a, sexp_B ) ) )
+ {
+ goto end;
+ }
+
+ // the shared key is a point stored compressed
+ // in an mpi and it will need to be decompressed
+ gcry_sexp_extract_param( sexp_shared_key, NULL, "s", &mpi_curve_shared_compressed, NULL );
+ if ( ( gcry_error = CurveDecompress( mpi_curve_shared_compressed, &mpi_curve_shared ) ) )
+ {
+ goto end;
+ }
+
+ gcry_mpi_aprint( GCRYMPI_FMT_USG, pp_bytes_shared, p_i_length_shared, mpi_curve_shared );
+ ReverseBuffer( *pp_bytes_shared, *p_i_length_shared );
+
+ end:
+ free( p_bytes_a_reversed );
+ if ( sexp_a != NULL )
+ gcry_sexp_release( sexp_a );
+ if ( sexp_B != NULL )
+ gcry_sexp_release( sexp_B );
+ if ( mpi_curve_shared_compressed != NULL )
+ gcry_mpi_release( mpi_curve_shared_compressed );
+
+ gcry_mpi_release( mpi_curve_shared );
+ return gcry_error;
+}
+
+static gcry_error_t CurveDecompress( gcry_mpi_t mpi_compressed, gcry_mpi_t *mpi_uncompressed )
+{
+ gcry_error_t gcry_error;
+
+ // to decompress, we decode it into a point
+ // then extract the X and discard the rest
+ gcry_ctx_t ctx_curve;
+ if ( ( gcry_error = gcry_mpi_ec_new( &ctx_curve, NULL, "Curve25519" ) ) )
+ {
+ return gcry_error;
+ }
+
+ gcry_mpi_point_t point_curve_shared = gcry_mpi_point_new( 0 );
+ if ( ( gcry_error = gcry_mpi_ec_decode_point( point_curve_shared, mpi_compressed, ctx_curve ) ) )
+ {
+ return gcry_error;
+ }
+
+ // we extract x, y and z but only need x because
+ // curve only uses the x coordinate.
+ gcry_mpi_t mpi_curve_shared_y = gcry_mpi_new( 0 );
+ gcry_mpi_t mpi_curve_shared_z = gcry_mpi_new( 0 );
+
+ gcry_mpi_point_snatch_get( *mpi_uncompressed, mpi_curve_shared_y,
+ mpi_curve_shared_z, point_curve_shared );
+ gcry_mpi_release( mpi_curve_shared_y );
+ gcry_mpi_release( mpi_curve_shared_z );
+
+ return 0;
+}
+
+static gcry_error_t CalculateAESSignature( const uint8_t *p_bytes_atv_challenge, size_t i_length_atv_challenge,
+ const uint8_t *p_bytes_ed_signature, size_t i_length_ed_signature,
+ const uint8_t *p_bytes_curve_shared, size_t i_length_curve_shared,
+ uint8_t **pp_bytes_aes_signature, size_t *p_i_length_aes_signature )
+{
+ gcry_error_t gcry_error;
+
+ uint8_t *p_bytes_aes_key;
+ uint8_t *p_bytes_aes_IV;
+ size_t i_length_aes_key_IV;
+
+ CalculateVerifyAESKeyIV( p_bytes_curve_shared, i_length_curve_shared, &p_bytes_aes_key, &p_bytes_aes_IV,
+ &i_length_aes_key_IV );
+
+ gcry_cipher_hd_t aes_handle;
+ gcry_cipher_open( &aes_handle, GCRY_CIPHER_AES128, GCRY_CIPHER_MODE_CTR, 0 );
+ gcry_cipher_setkey( aes_handle, p_bytes_aes_key, i_length_aes_key_IV );
+ gcry_cipher_setiv( aes_handle, p_bytes_aes_IV, i_length_aes_key_IV );
+ gcry_cipher_setctr( aes_handle, p_bytes_aes_IV, i_length_aes_key_IV );
+
+ *p_i_length_aes_signature = 64;
+ *pp_bytes_aes_signature = calloc( *p_i_length_aes_signature, sizeof( **pp_bytes_aes_signature ) );
+
+ // encrypt atv_data and discard then encrypt p_bytes_ed_signature so that the sig is correct
+ if ( ( gcry_error = gcry_cipher_encrypt( aes_handle, *pp_bytes_aes_signature, *p_i_length_aes_signature,
+ p_bytes_atv_challenge, i_length_atv_challenge ) ) )
+ {
+ return gcry_error;
+ }
+ if ( ( gcry_error = gcry_cipher_encrypt( aes_handle, *pp_bytes_aes_signature, *p_i_length_aes_signature,
+ p_bytes_ed_signature, i_length_ed_signature ) ) )
+ {
+ return gcry_error;
+ }
+
+ gcry_cipher_close( aes_handle );
+ free( p_bytes_aes_IV );
+ free( p_bytes_aes_key );
+
+ return 0;
+}
+
+static void CalculateVerifyAESKeyIV( const uint8_t *p_bytes_curve_shared, size_t i_length_curve_shared,
+ uint8_t **p_bytes_aes_key, uint8_t **p_bytes_aes_IV,
+ size_t *p_i_length_aes_key_IV )
+{
+ *p_i_length_aes_key_IV = 16;
+
+ gcry_md_hd_t digest_handle_key;
+ gcry_md_open( &digest_handle_key, GCRY_MD_SHA512, 0 );
+ gcry_md_write( digest_handle_key, "Pair-Verify-AES-Key", strlen( "Pair-Verify-AES-Key" ) );
+ gcry_md_write( digest_handle_key, p_bytes_curve_shared, i_length_curve_shared );
+
+ gcry_md_hd_t digest_handle_IV;
+ gcry_md_open( &digest_handle_IV, GCRY_MD_SHA512, 0 );
+ gcry_md_write( digest_handle_IV, "Pair-Verify-AES-IV", strlen( "Pair-Verify-AES-IV" ) );
+ gcry_md_write( digest_handle_IV, p_bytes_curve_shared, i_length_curve_shared );
+
+ *p_bytes_aes_key = calloc( *p_i_length_aes_key_IV, sizeof( **p_bytes_aes_key ) );
+ *p_bytes_aes_IV = calloc( *p_i_length_aes_key_IV, sizeof( **p_bytes_aes_IV ) );
+
+ memcpy( *p_bytes_aes_key, gcry_md_read( digest_handle_key, GCRY_MD_SHA512 ), *p_i_length_aes_key_IV );
+ memcpy( *p_bytes_aes_IV, gcry_md_read( digest_handle_IV, GCRY_MD_SHA512 ), *p_i_length_aes_key_IV );
+
+ gcry_md_close( digest_handle_key );
+ gcry_md_close( digest_handle_IV );
+}
+
+#ifdef AIRPLAY_TEST
+
+static void HexToBytes( const char *psz_hex, uint8_t **p_bytes, size_t *i_length )
+{
+ gcry_mpi_t mpi = gcry_mpi_new( 0 );
+ gcry_mpi_scan( &mpi, GCRYMPI_FMT_HEX, psz_hex, 0, NULL );
+ gcry_mpi_aprint( GCRYMPI_FMT_USG, p_bytes, i_length, mpi );
+}
+
+static bool TestCrypto()
+{
+ bool pass = true;
+
+ if ( !TestPair( "5QO6BSB38B2XKFDI", "1364", "550a86d201376674ee8ce46ef472db01",
+ "1995b7a4f0d364de3a57274f62e635b7c8bdd3123063e3273dbf26a8e2e7d03a93a037af9431dae8d11fa293e4a6e167b2"
+ "3709f313efe3ae1a6c6b58066f823fff8293c613c6447bf787b7db6eae9aeb69316fc40edeb8847b76c89a62f7820a762b"
+ "e97fd8ddb503663ae63e538c1b6ee80cc8faa6a678729b9f0391514c4f9ba524baef1aeb887f847fe884b729cb305f6e78"
+ "4b08c318f29c09bffb97281cbff7c3099a235464c9ec8f471117c0e9bf9f870b2e1de0a21e949fc3015d66953b983e484e"
+ "451087b315f771147e273408e4bad42341c5786290454c89ffce77915002fd5b8ce4c1c13b039081f79749426c5da7d889"
+ "c05b60d5952938af491854",
+ "45f30247f402972ba648f65592434026bc09754e5069dd311b198d6eba0f9b4ec1b4bcf7c618a4761845a6b9d591458b7c"
+ "aaf453466aa72185718a0f961b8171e4eb28d5312d610c85aa7eff3607a0329cf8073788ca707da80c5a7776185001587d"
+ "f44ef9973cacd7872b291ed8607df576f2c286d776d88fe5281362daca2328de9c7f4a0729dcb2e835b28b8323e6383044"
+ "65ba09ee3fbf16f42624a15b660a3d2a40dfc4e96df9a10ed9ce3cca380189584543bbbd46428489fbadccaf47791c51da"
+ "4aa928b496e4b0fd3b1eec42b47740cc9b6c9bdfe968fb74c1eb014e33b2d6b7f4c43e97f24b751ce2364c042eff678cba"
+ "70f9945190e42e9cf78421",
+ "7eb98a04ddb068e99a11c374081e6ea63231841a45e591890217842e880f8beb7a7c0505ac796c58",
+ "2bc212e99d4cb440ed318827c1348fc05120bfe7", "aabe40b9027e3a0cbaaa8a751ac335bd0273b577",
+ "62b360b740800423e348996afbda4425", "12208dc4cd054ec1bb657ee48fc9ffb4",
+ "a8562f18c1d7b6294b9cfb5f37602e2f95bf6c0a1ec835b78e051886b32a2043",
+ "90c867c8f8f55dc37b25fcd20be27e4f549df0f120422312a91e0f4afda8d51d",
+ "eacbf4ba605b9ef7918b513f0e007223" ) )
+ {
+ printf( "Pair not correct" );
+ pass = false;
+ }
+
+ if ( !TestVerify( "e65aa4bd200d7fb26e7c64803a6cefdab868c09b716fb9a922b8bb5e7ec9102b",
+ "beadc555fabb47d3fad99ed1f0c11f803730a1f27ca8360804a49466c12460a1",
+ "122894d5bd9e38e0bb9e47a29657bdf4ef6a410c901ea4c50f501c66d21690da3df482c712446cf432415274220b6a5008834d70f3c7efcaa5f0a80ebb888cda",
+ "687e235342da53b413de4ab8f64ef794fa1b67e43d52701fd16ab962ef35356f",
+ "f6739cba89bd5e644b113ff56c2ca399e0e2f08a6bca83f03ccd6b90a5dfd841",
+ "78f8f68c6da6c66f4b9b893d9ba03b31594e2dd38026f65d7b7b8953ecb6717c",
+ "b7401d839100cee68fc995e43a5e2df9406bf6df8d209f62ebe5783f109f760",
+ "697c6a74bca8c8f37775e50e9e2b0e93", "8a4fd50e8e2ceefb25f9fc2594b2027b",
+ "bdcbb15a8b7e08888c3370a2a3f8ca1e9292bc8d3c18547b4ee0ba3ef834d8e7315f665f5e04f481fac923567de27c56800c72862b00ea367bc83b5b6269ca09",
+ "49239fd6879f6c1a72fe8949e225d1dcf5c0861f81e8f9b70b4f9a6ab480acb0b263454194747c726f2679e81b9a5a1f0b210a0d3c609073f84895dc88c03b80" ) )
+ {
+ printf( "Verify not correct" );
+ pass = false;
+ }
+
+ if ( !TestVerify( "766ad48e3de029d548be8da9f1de10bee4af2b8e85414b801d5c19b976d494e8",
+ "30553b3a95eb3569124a58786909559f8002f69d6c412cb68c43078e4f88e6fd",
+ "6ca48af0ff662349123bab2d97f118e8cebb12bb9cb6370c08bfa78a7d5c3d8ab8c8481618932ea6653f5f3005fdc9555492a74230db2a700338a534e601db25",
+ "d5406e72caa0c8b48ebce34d6243ea44613b5e3b3b6cec63b436b5e8b1735c04",
+ "772e71554c5ea8a5592ac209af299ef9d4c767d223c160051d3b4d457e750509",
+ "e0e65f15be4f1a5d0c3e74a33debdbb3f735170c298b967ab4211d05e9d30767",
+ "d2ad4edc061cd76899794a34aca6a6e6b4d4c4db232896712b6f6b27c5a06c15",
+ "17aec1b52efa336168e90ab4ed03662b",
+ "7b007b405d383edc786e3059fc427541",
+ "a8819bc494cad169b41254675e4edaca056c23b9b3557163c7007b5297b99596ef3a29997cf18d8129f170f6d6c8b2f45af6252f0c0f883059f5b8fbc46b003",
+ "ab4d034f4438fc9c75a163f077e918d5dde130f68f1f334b3c472923474c97a0720223859b7b0794584d6e322b9741edfccb24a7a864b089be2806f623cd01a6" ) )
+ {
+ printf( "Verify not correct" );
+ pass = false;
+ }
+
+ return pass;
+}
+
+static bool TestPair( const char *psz_I, const char *psz_P, const char *psz_hex_s, const char *psz_hex_B,
+ const char *psz_hex_a, const char *psz_hex_K, const char *psz_hex_M1, const char *psz_hex_M2,
+ const char *psz_hex_aes_key, const char *psz_hex_aes_IV, const char *psz_hex_ed25519,
+ const char *psz_hex_epk, const char *psz_hex_signature )
+{
+ bool pass = true;
+
+ struct srp_user *p_user = apple_srp_user_new( psz_I, strlen( psz_I ), psz_P, strlen( psz_P ) );
+
+ gcry_mpi_t mpi_a = gcry_mpi_new( 0 );
+ gcry_mpi_scan( &mpi_a, GCRYMPI_FMT_HEX, psz_hex_a, 0, NULL );
+
+ uint8_t *p_bytes_A = NULL;
+ size_t i_A_length = 0;
+ srp_user_start_authentication( p_user, &mpi_a, &p_bytes_A, &i_A_length );
+
+ uint8_t *p_bytes_s = NULL;
+ size_t i_length_s = 0;
+ uint8_t *p_bytes_B = NULL;
+ size_t i_length_B = 0;
+ HexToBytes( psz_hex_s, &p_bytes_s, &i_length_s );
+ HexToBytes( psz_hex_B, &p_bytes_B, &i_length_B );
+
+ uint8_t *p_bytes_M1 = NULL;
+ size_t i_length_M1 = 0;
+ srp_user_process_challenge( p_user, p_bytes_s, i_length_s, p_bytes_B, i_length_B, &p_bytes_M1, &i_length_M1 );
+
+ uint8_t *p_bytes_K;
+ size_t i_length_K;
+ srp_user_get_hashed_session_key( p_user, &p_bytes_K, &i_length_K );
+
+ uint8_t *p_bytes_K_Expected;
+ size_t i_length_K_Expected;
+ HexToBytes( psz_hex_K, &p_bytes_K_Expected, &i_length_K_Expected );
+
+ if ( i_length_K != i_length_K_Expected )
+ {
+ printf( "K lengths not equal." );
+ pass = false;
+ }
+
+ if ( memcmp( p_bytes_K, p_bytes_K_Expected, i_length_K ) != 0 )
+ {
+ printf( "K content not equal." );
+ pass = false;
+ }
+
+ // assert M1 == M1Expected
+ uint8_t *p_bytes_M1_Expected;
+ size_t i_length_M1_Expected;
+ HexToBytes( psz_hex_M1, &p_bytes_M1_Expected, &i_length_M1_Expected );
+
+ if ( i_length_M1 != i_length_M1_Expected )
+ {
+ printf( "M1 lengths not equal." );
+ pass = false;
+ }
+
+ if ( memcmp( p_bytes_M1, p_bytes_M1_Expected, i_length_M1 ) != 0 )
+ {
+ printf( "M1 content not equal." );
+ pass = false;
+ }
+
+ // verify M2
+ uint8_t *p_bytes_M2 = NULL;
+ size_t i_length_M2;
+ HexToBytes( psz_hex_M2, &p_bytes_M2, &i_length_M2 );
+
+ if ( !srp_user_verify_session( p_user, p_bytes_M2 ) )
+ {
+ printf( "M2 not valid" );
+ pass = false;
+ }
+
+ // generate AES key and IV
+ uint8_t *p_aes_key;
+ uint8_t *p_aes_IV;
+ size_t i_length_key_IV;
+ CalculatePairAESKeyIV( p_user, &p_aes_key, &p_aes_IV, &i_length_key_IV );
+
+ uint8_t *p_aes_key_Expected;
+ size_t i_aes_key_Expected;
+ HexToBytes( psz_hex_aes_key, &p_aes_key_Expected, &i_aes_key_Expected );
+
+ uint8_t *p_aes_IV_Expected;
+ size_t i_aes_IV_Expected;
+ HexToBytes( psz_hex_aes_IV, &p_aes_IV_Expected, &i_aes_IV_Expected );
+
+ if ( memcmp( p_aes_key, p_aes_key_Expected, i_length_key_IV ) != 0 )
+ {
+ printf( "AES Key content not equal." );
+ pass = false;
+ }
+
+ if ( memcmp( p_aes_IV, p_aes_IV_Expected, i_length_key_IV ) != 0 )
+ {
+ printf( "AES IV not equal." );
+ pass = false;
+ }
+
+ uint8_t *p_bytes_epk;
+ uint8_t *p_bytes_authTag;
+
+ uint8_t *p_bytes_ed25519;
+ size_t i_length_ed25519;
+ HexToBytes( psz_hex_ed25519, &p_bytes_ed25519, &i_length_ed25519 );
+
+ EncryptEd25519( p_user, p_bytes_ed25519, i_length_ed25519, &p_bytes_epk, &p_bytes_authTag );
+
+ uint8_t *p_bytes_epk_Expected;
+ HexToBytes( psz_hex_epk, &p_bytes_epk_Expected, NULL );
+
+ uint8_t *p_bytes_authTag_Expected;
+ HexToBytes( psz_hex_signature, &p_bytes_authTag_Expected, NULL );
+
+ if ( memcmp( p_bytes_epk, p_bytes_epk_Expected, i_length_key_IV ) != 0 )
+ {
+ printf( "epk not equal." );
+ pass = false;
+ }
+
+ if ( memcmp( p_bytes_authTag, p_bytes_authTag_Expected, i_length_key_IV ) != 0 )
+ {
+ printf( "authTag not equal." );
+ pass = false;
+ }
+
+ return pass;
+}
+
+static bool TestVerify( const char *psz_hex_ed_priv, const char *psz_hex_ed_pub, const char *psz_hex_atv_challenge,
+ const char *psz_hex_atv_curve_pub, const char *psz_hex_curve_pub,
+ const char *psz_hex_curve_priv, const char *psz_hex_curve_shared, const char *psz_hex_aes_key,
+ const char *psz_hex_aes_IV, const char *psz_hex_ed_signature,
+ const char *psz_hex_aes_signature )
+{
+ bool pass = true;
+
+ // verify Curve25519 diffie helman
+
+ if ( !TestCurveDH( psz_hex_curve_priv, psz_hex_atv_curve_pub, psz_hex_curve_shared ) )
+ {
+ printf( "Curve incorrect" );
+ pass = false;
+ }
+
+ // verify aes IV and key
+
+ if ( !TestAESKeyIV( psz_hex_curve_shared, psz_hex_aes_key, psz_hex_aes_IV ) )
+ {
+ printf( "AES Key or IV incorrect" );
+ pass = false;
+ }
+
+ // test ED sign
+
+ if ( !TestEdSign( psz_hex_ed_priv, psz_hex_ed_pub, psz_hex_curve_pub, psz_hex_atv_curve_pub,
+ psz_hex_ed_signature ) )
+ {
+ printf( "ED Signature Failed" );
+ pass = false;
+ }
+
+ // verify AES signature
+
+ if ( !TestAESSignature( psz_hex_atv_challenge, psz_hex_curve_shared, psz_hex_ed_signature, psz_hex_aes_signature ) )
+ {
+ printf( "AES Signature failed" );
+ pass = false;
+ };
+
+ return pass;
+}
+
+static bool TestAESSignature( const char *psz_hex_atv_challenge, const char *psz_hex_curve_shared,
+ const char *psz_hex_ed_signature, const char *psz_hex_aes_signature )
+{
+ bool pass = true;
+
+ uint8_t *p_bytes_atv_challenge = NULL;
+ size_t i_length_atv_challenge;
+ HexToBytes( psz_hex_atv_challenge, &p_bytes_atv_challenge, &i_length_atv_challenge );
+
+ uint8_t *p_bytes_ed_signature = NULL;
+ size_t i_length_ed_signature;
+ HexToBytes( psz_hex_ed_signature, &p_bytes_ed_signature, &i_length_ed_signature );
+
+ uint8_t *p_bytes_curve_shared = NULL;
+ size_t i_length_curve_shared;
+ HexToBytes( psz_hex_curve_shared, &p_bytes_curve_shared, &i_length_curve_shared );
+
+ uint8_t *p_bytes_aes_signature = NULL;
+ size_t i_length_aes_signature;
+
+ CalculateAESSignature( p_bytes_atv_challenge, i_length_atv_challenge, p_bytes_ed_signature, i_length_ed_signature,
+ p_bytes_curve_shared, i_length_curve_shared, &p_bytes_aes_signature,
+ &i_length_aes_signature );
+
+ uint8_t *p_bytes_aes_signature_expected = NULL;
+ size_t i_length_aes_signature_expected;
+ HexToBytes( psz_hex_aes_signature, &p_bytes_aes_signature_expected, &i_length_aes_signature_expected );
+
+ if ( i_length_aes_signature != i_length_aes_signature_expected )
+ {
+ printf( "AES Signature invalid length" );
+ pass = false;
+ }
+
+ if ( memcmp( p_bytes_aes_signature, p_bytes_aes_signature_expected, i_length_aes_signature ) != 0 )
+ {
+ printf( "AES Signature not expected" );
+ pass = false;
+ }
+
+ free( p_bytes_aes_signature );
+ free( p_bytes_aes_signature_expected );
+ free( p_bytes_curve_shared );
+
+ return pass;
+}
+
+static bool TestAESKeyIV( const char *psz_hex_curve_shared, const char *psz_hex_aes_key, const char *psz_hex_aes_IV )
+{
+ bool pass = true;
+
+ uint8_t *p_bytes_curve_shared = NULL;
+ size_t i_length_curve_shared;
+ HexToBytes( psz_hex_curve_shared, &p_bytes_curve_shared, &i_length_curve_shared );
+
+ uint8_t *p_bytes_aes_key;
+ uint8_t *p_bytes_aes_IV;
+ size_t i_length_aes_key_IV;
+ CalculateVerifyAESKeyIV( p_bytes_curve_shared, i_length_curve_shared, &p_bytes_aes_key, &p_bytes_aes_IV,
+ &i_length_aes_key_IV );
+
+ uint8_t *p_bytes_aes_key_expected;
+ size_t i_length_aes_key_expected;
+ HexToBytes( psz_hex_aes_key, &p_bytes_aes_key_expected, &i_length_aes_key_expected );
+
+ uint8_t *p_bytes_aes_IV_expected;
+ size_t i_length_aes_IV_expected;
+ HexToBytes( psz_hex_aes_IV, &p_bytes_aes_IV_expected, &i_length_aes_IV_expected );
+
+ if ( i_length_aes_key_IV != i_length_aes_key_expected )
+ {
+ printf( "AES Key invalid length" );
+ pass = false;
+ }
+
+ if ( memcmp( p_bytes_aes_key, p_bytes_aes_key_expected, i_length_aes_key_IV ) != 0 )
+ {
+ printf( "AES Key not expected" );
+ pass = false;
+ }
+
+ if ( i_length_aes_key_IV != i_length_aes_IV_expected )
+ {
+ printf( "AES IV invalid length" );
+ pass = false;
+ }
+
+ if ( memcmp( p_bytes_aes_IV, p_bytes_aes_IV_expected, i_length_aes_key_IV ) != 0 )
+ {
+ printf( "AES IV not expected" );
+ pass = false;
+ }
+
+ free( p_bytes_curve_shared );
+ free( p_bytes_aes_IV );
+ free( p_bytes_aes_IV_expected );
+ free( p_bytes_aes_key );
+ free( p_bytes_aes_key_expected );
+
+ return pass;
+}
+
+static bool TestCurveDH( const char *psz_hex_a, const char *psz_hex_B, const char *psz_hex_shared )
+{
+ bool pass = true;
+
+ uint8_t *p_bytes_a = NULL;
+ size_t i_length_a;
+ HexToBytes( psz_hex_a, &p_bytes_a, &i_length_a );
+
+ uint8_t *p_bytes_B = NULL;
+ size_t i_length_B;
+ HexToBytes( psz_hex_B, &p_bytes_B, &i_length_B );
+
+ uint8_t *p_bytes_shared = NULL;
+ size_t i_length_shared;
+ Curve25519DiffieHellman( p_bytes_a, i_length_a, p_bytes_B, i_length_B, &p_bytes_shared, &i_length_shared );
+
+ uint8_t *p_bytes_shared_expected = NULL;
+ size_t i_length_shared_expected;
+ HexToBytes( psz_hex_shared, &p_bytes_shared_expected, &i_length_shared_expected );
+
+ if ( i_length_shared != i_length_shared_expected )
+ {
+ printf( "Curve invalid length" );
+ pass = false;
+ }
+
+ if ( memcmp( p_bytes_shared, p_bytes_shared_expected, i_length_shared ) != 0 )
+ {
+ printf( "Curve not expected" );
+ pass = false;
+ }
+
+ return pass;
+}
+
+static bool TestEdSign( const char *psz_hex_ed_priv, const char *psz_hex_ed_pub, const char *psz_hex_curve_pub,
+ const char *psz_hex_atv_curve_pub, const char *psz_hex_ed_signature )
+{
+ bool pass = true;
+ gcry_error_t gcry_error;
+
+ uint8_t *p_bytes_ed_priv;
+ size_t i_length_ed_priv;
+ HexToBytes( psz_hex_ed_priv, &p_bytes_ed_priv, &i_length_ed_priv );
+
+ uint8_t *p_bytes_ed_pub;
+ size_t i_length_ed_pub;
+ HexToBytes( psz_hex_ed_pub, &p_bytes_ed_pub, &i_length_ed_pub );
+
+ gcry_sexp_t sexp_ed = NULL;
+ gcry_error = gcry_sexp_build( &sexp_ed, NULL,
+ "("
+ " key-data"
+ " (public-key"
+ " (ecc"
+ " (curve \"Ed25519\")"
+ " (flags eddsa)"
+ " (q%b)"
+ " )"
+ " )"
+ " (private-key"
+ " (ecc"
+ " (curve \"Ed25519\")"
+ " (flags eddsa)"
+ " (q%b)"
+ " (d%b)"
+ " )"
+ " )"
+ ")",
+ i_length_ed_pub, p_bytes_ed_pub,
+ i_length_ed_pub, p_bytes_ed_pub,
+ i_length_ed_priv, p_bytes_ed_priv );
+
+ uint8_t *p_bytes_curve_pub;
+ size_t i_length_curve_pub;
+ HexToBytes( psz_hex_curve_pub, &p_bytes_curve_pub, &i_length_curve_pub );
+
+ uint8_t *p_bytes_atv_curve_pub;
+ size_t i_length_atv_curve_pub;
+ HexToBytes( psz_hex_atv_curve_pub, &p_bytes_atv_curve_pub, &i_length_atv_curve_pub );
+
+ uint8_t *p_bytes_ed_signature;
+ size_t i_length_ed_signature;
+ EdSignConcatenatedBytes( sexp_ed, p_bytes_curve_pub, i_length_curve_pub,
+ p_bytes_atv_curve_pub, i_length_atv_curve_pub,
+ &p_bytes_ed_signature, &i_length_ed_signature );
+
+ uint8_t *p_bytes_ed_signature_expected;
+ size_t i_length_ed_signature_expected;
+ HexToBytes( psz_hex_ed_signature, &p_bytes_ed_signature_expected, &i_length_ed_signature_expected );
+
+ if ( i_length_ed_signature != i_length_ed_signature_expected )
+ {
+ printf( "Ed Signature invalid length" );
+ pass = false;
+ }
+
+ if ( memcmp( p_bytes_ed_signature, p_bytes_ed_signature_expected, i_length_ed_signature ) != 0 )
+ {
+ printf( "Ed Signature not expected" );
+ pass = false;
+ }
+
+ return pass;
+}
+
+#endif
diff --git a/modules/stream_out/airplay/csrp/srp.c b/modules/stream_out/airplay/csrp/srp.c
new file mode 100755
index 0000000000..1527d35fc7
--- /dev/null
+++ b/modules/stream_out/airplay/csrp/srp.c
@@ -0,0 +1,955 @@
+/*
+ * A Secure Remote Password 6a implementation (RFC 5054)
+ * Also includes Apple-Flavoured SRP which changes a
+ * few of the functions to be compatible with Apple TVs.
+ *
+ * Adapted from CSRP by Tom Cocagne by Alexander Lyon.
+ * https://github.com/cocagne/csrp
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2013 Tom Cocagne, Alexander Lyon
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is furnished to do
+ * so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+/*
+ * Below are the symbols used in the code:
+ *
+ * Symbol | Description
+ * --------- | -----------
+ * H(x) | One-way hash function (SHA1)
+ * PAD(x, y) | Left pad x to nearest byte, length y
+ * ^ | Exponentiation
+ * ~ | Concatenation
+ *
+ * Symbol | Source | Calculation | Description
+ * -- | ------------------ | ------------------------ | --------------------------------
+ * N | Predetermined | | A large safe prime (N = 2q+1, where q is prime)
+ * g | Predetermined | | A generator
+ * a | Generated by Client | | Client ephemeral secret key
+ * b | Generated by Server | | Server ephemeral secret key
+ * I | Supplied by User | | Username or Identity
+ * P | Supplied by User | | Cleartext Password
+ * s | Generated by Server | | Salt
+ * k | Calculated | `H(PAD(N, N) ~ PAD(g, N))` | SRP-6 multiplier
+ * x | Calculated | `H(s ~ H( I ~ ':' ~ P ))` | Used to calculate the verifier and shared session key
+ * v | Calculated | `g^x % N` | Server password verifier
+ * B | Calculated by Server | `(k * v + g^b) % N` | Server ephemeral public key
+ * A | Calculated by Client | `g^a % N` | Client ephemeral public key
+ * u | Calculated | `H(A ~ B)` | Random scrambling parameter
+ * S<sub>Client</sub> | Calculated | `(B - k * g^x) ^ (a + u * x) % N` |
+ * S<sub>Server</sub> | Calculated | `(A * v^u) ^ b % N` |
+ * K | Calculated | `H(H(S) ~ 00000000) ~ H(H(S) ~ 00000001)` | Shared Key
+ * M1 | Calculated | `H(H(N) xor H(g) ~ H(I) ~ s ~ A ~ B ~ K)` | Client key verifier
+ * M2 | Calculated | `H(A ~ M1 ~ K)` | Server key verifier
+ */
+
+#include <assert.h>
+#include <gcrypt.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/time.h>
+
+#include "srp.h"
+
+typedef struct
+{
+ gcry_mpi_t N;
+ gcry_mpi_t g;
+} Ng_pair_t;
+
+typedef struct
+{
+ const char *psz_N;
+ const char *psz_g;
+} Ng_hex_pair_t;
+
+/* All constants here were pulled from Appendix A of RFC 5054 */
+static Ng_hex_pair_t Ng_hex_pairs[] = {
+ {
+ /* 1024 */
+ "EEAF0AB9ADB38DD69C33F80AFA8FC5E86072618775FF3C0B9EA2314C9C256576D674DF7496"
+ "EA81D3383B4813D692C6E0E0D5D8E250B98BE48E495C1D6089DAD15DC7D7B46154D6B6CE8E"
+ "F4AD69B15D4982559B297BCF1885C529F566660E57EC68EDBC3C05726CC02FD4CBF4976EAA"
+ "9AFD5138FE8376435B9FC61D2FC0EB06E3",
+ "2"
+ },
+ {
+ /* 2048 */
+ "AC6BDB41324A9A9BF166DE5E1389582FAF72B6651987EE07FC3192943DB56050A37329CBB4"
+ "A099ED8193E0757767A13DD52312AB4B03310DCD7F48A9DA04FD50E8083969EDB767B0CF60"
+ "95179A163AB3661A05FBD5FAAAE82918A9962F0B93B855F97993EC975EEAA80D740ADBF4FF"
+ "747359D041D5C33EA71D281E446B14773BCA97B43A23FB801676BD207A436C6481F1D2B907"
+ "8717461A5B9D32E688F87748544523B524B0D57D5EA77A2775D2ECFA032CFBDBF52FB37861"
+ "60279004E57AE6AF874E7303CE53299CCC041C7BC308D82A5698F3A8D0C38271AE35F8E9DB"
+ "FBB694B5C803D89F7AE435DE236D525F54759B65E372FCD68EF20FA7111F9E4AFF73",
+ "2"
+ },
+ {
+ /* 4096 */
+ "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E08"
+ "8A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B"
+ "302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9"
+ "A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE6"
+ "49286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8"
+ "FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D"
+ "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C"
+ "180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718"
+ "3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D"
+ "04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7D"
+ "B3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D226"
+ "1AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200C"
+ "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFC"
+ "E0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B26"
+ "99C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB"
+ "04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2"
+ "233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127"
+ "D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199"
+ "FFFFFFFFFFFFFFFF",
+ "5"
+ },
+ {
+ /* 8192 */
+ "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E08"
+ "8A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B"
+ "302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9"
+ "A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE6"
+ "49286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8"
+ "FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D"
+ "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C"
+ "180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718"
+ "3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D"
+ "04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7D"
+ "B3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D226"
+ "1AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200C"
+ "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFC"
+ "E0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B26"
+ "99C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB"
+ "04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2"
+ "233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127"
+ "D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934028492"
+ "36C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406"
+ "AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918"
+ "DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B33205151"
+ "2BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03"
+ "F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97F"
+ "BEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AA"
+ "CC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58B"
+ "B7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632"
+ "387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E"
+ "6DBE115974A3926F12FEE5E438777CB6A932DF8CD8BEC4D073B931BA"
+ "3BC832B68D9DD300741FA7BF8AFC47ED2576F6936BA424663AAB639C"
+ "5AE4F5683423B4742BF1C978238F16CBE39D652DE3FDB8BEFC848AD9"
+ "22222E04A4037C0713EB57A81A23F0C73473FC646CEA306B4BCBC886"
+ "2F8385DDFA9D4B7FA2C087E879683303ED5BDD3A062B3CF5B3A278A6"
+ "6D2A13F83F44F82DDF310EE074AB6A364597E899A0255DC164F31CC5"
+ "0846851DF9AB48195DED7EA1B1D510BD7EE74D73FAF36BC31ECFA268"
+ "359046F4EB879F924009438B481C6CD7889A002ED5EE382BC9190DA6"
+ "FC026E479558E4475677E9AA9E3050E2765694DFC81F56E880B96E71"
+ "60C980DD98EDD3DFFFFFFFFFFFFFFFFF",
+ "13"
+ },
+ {
+ /* null sentinel */
+ 0,
+ 0
+ }
+};
+
+static Ng_pair_t *new_ng( srp_Ng_type pair_type, const char *psz_N_hex, const char *psz_g_hex )
+{
+ Ng_pair_t *ng = malloc( sizeof( *ng ) );
+ const char *p_source = psz_N_hex == NULL ? Ng_hex_pairs[pair_type].psz_N : psz_N_hex;
+ const char *g_source = psz_g_hex == NULL ? Ng_hex_pairs[pair_type].psz_g : psz_g_hex;
+
+ gcry_error_t error = 0;
+ error |= gcry_mpi_scan( &ng->N, GCRYMPI_FMT_HEX, p_source, 0, NULL );
+ error |= gcry_mpi_scan( &ng->g, GCRYMPI_FMT_HEX, g_source, 0, NULL );
+ return error == 0 ? ng : NULL;
+}
+
+static void delete_ng( Ng_pair_t **pp_pair )
+{
+ if ( *pp_pair != NULL )
+ {
+ gcry_mpi_release( ( *pp_pair )->N );
+ gcry_mpi_release( ( *pp_pair )->g );
+ free( *pp_pair );
+ }
+ *pp_pair = NULL;
+}
+
+struct srp_verifier
+{
+ srp_hash_algorithm hash_alg;
+ Ng_pair_t *p_Ng_pair;
+ bool b_authenticated;
+
+ char *psz_username;
+ uint8_t *p_bytes_B;
+
+ uint8_t *p_bytes_M1;
+ uint8_t *p_bytes_M2;
+ uint8_t *p_bytes_K;
+};
+
+struct srp_user
+{
+ srp_hash_algorithm hash_alg;
+ Ng_pair_t *p_ng;
+
+ gcry_mpi_t a;
+ gcry_mpi_t A;
+ gcry_mpi_t S;
+
+ const uint8_t *p_bytes_A;
+ bool b_authenticated;
+
+ char *psz_username;
+ char *psz_password;
+ size_t i_password_len;
+ size_t i_username_len;
+
+ uint8_t *p_bytes_M1; // client verifier
+ uint8_t *p_bytes_M2; // server verifier
+
+ uint8_t *p_bytes_K; // shared key
+ size_t i_length_K;
+
+ void (*fp_calculate_K)( srp_hash_algorithm alg, gcry_mpi_t S, uint8_t **pp_bytes_K, size_t *p_i_length_K );
+
+ void (*fp_calculate_M1)( struct srp_user *p_user, gcry_mpi_t s, gcry_mpi_t B, uint8_t **pp_bytes_M1,
+ size_t *pi_length_M1 );
+};
+
+static int srp_hash_to_gcrypt( srp_hash_algorithm alg )
+{
+ switch ( alg )
+ {
+ case SRP_SHA1 :
+ return GCRY_MD_SHA1;
+ case SRP_SHA224:
+ return GCRY_MD_SHA224;
+ case SRP_SHA256:
+ return GCRY_MD_SHA256;
+ case SRP_SHA384:
+ return GCRY_MD_SHA384;
+ case SRP_SHA512:
+ return GCRY_MD_SHA512;
+ default:
+ return -1;
+ };
+}
+
+static size_t mpi_nbytes( gcry_mpi_t mpi )
+{
+ return ( gcry_mpi_get_nbits( mpi ) + 7 ) / 8;
+}
+
+// H(p_bytes_in)
+static void hash_bytes( srp_hash_algorithm alg, const uint8_t *p_bytes_in, size_t i_length, uint8_t **pp_bytes_out )
+{
+ size_t i_digest_size = srp_hash_length( alg );
+ *pp_bytes_out = calloc( i_digest_size, sizeof( **pp_bytes_out ) );
+ gcry_md_hash_buffer( srp_hash_to_gcrypt( alg ), *pp_bytes_out, p_bytes_in, i_length );
+}
+
+// H(mpi_to_bin(n))
+static void hash_mpi( srp_hash_algorithm alg, gcry_mpi_t n, uint8_t **pp_bytes_out, size_t *pi_length_out )
+{
+ size_t i_length = mpi_nbytes( n );
+ if ( pi_length_out != NULL ) *pi_length_out = i_length;
+
+ uint8_t *p_bytes = calloc( i_length, sizeof( *p_bytes ) );
+ gcry_mpi_print( GCRYMPI_FMT_USG, p_bytes, i_length, NULL, n );
+
+ hash_bytes( alg, p_bytes, i_length, pp_bytes_out );
+ free( p_bytes );
+}
+
+// H(mpi_to_bin(n1) | mpi_to_bin(n2))
+static gcry_mpi_t *hash_mpi_concat_mpi( srp_hash_algorithm alg, gcry_mpi_t n1, gcry_mpi_t n2 )
+{
+ uint8_t *p_hashed_bytes = NULL;
+ size_t i_len_n1 = mpi_nbytes( n1 );
+ size_t i_len_n2 = mpi_nbytes( n2 );
+ uint8_t *n_n1_bytes = calloc( i_len_n1, sizeof( *n_n1_bytes ) );
+ uint8_t *n_n2_bytes = calloc( i_len_n2, sizeof( *n_n2_bytes ) );
+
+ gcry_mpi_print( GCRYMPI_FMT_USG, n_n1_bytes, i_len_n1, NULL, n1 );
+ gcry_mpi_print( GCRYMPI_FMT_USG, n_n2_bytes, i_len_n2, NULL, n2 );
+
+ gcry_md_hd_t digest_handle;
+ gcry_md_open( &digest_handle, srp_hash_to_gcrypt( alg ), 0 );
+ gcry_md_write( digest_handle, n_n1_bytes, i_len_n1 );
+ gcry_md_write( digest_handle, n_n2_bytes, i_len_n2 );
+ p_hashed_bytes = gcry_md_read( digest_handle, srp_hash_to_gcrypt( alg ) );
+
+ gcry_mpi_t *ret = malloc( sizeof( *ret ) );
+ gcry_mpi_scan( ret, GCRYMPI_FMT_USG, p_hashed_bytes, srp_hash_length( alg ), NULL );
+ gcry_md_close( digest_handle );
+
+ free( n_n1_bytes );
+ free( n_n2_bytes );
+ return ret;
+}
+
+// H(PAD(n1) | PAD(n2))
+static gcry_mpi_t *hash_mpi_concat_mpi_padded( srp_hash_algorithm alg, size_t i_pad_length,
+ gcry_mpi_t n1, gcry_mpi_t n2 )
+{
+ uint8_t *p_hashed_bytes = NULL;
+ size_t i_len_n1 = mpi_nbytes( n1 );
+ size_t i_len_n2 = mpi_nbytes( n2 );
+
+ assert( i_len_n1 <= i_pad_length );
+ assert( i_len_n2 <= i_pad_length );
+
+ uint8_t *n_n1_bytes = calloc( i_pad_length, sizeof( *n_n1_bytes ) );
+ uint8_t *n_n2_bytes = calloc( i_pad_length, sizeof( *n_n2_bytes ) );
+
+ gcry_mpi_print( GCRYMPI_FMT_USG, n_n1_bytes + i_pad_length - i_len_n1, i_len_n1, NULL, n1 );
+ gcry_mpi_print( GCRYMPI_FMT_USG, n_n2_bytes + i_pad_length - i_len_n2, i_len_n2, NULL, n2 );
+
+ gcry_md_hd_t digest_handle;
+ gcry_md_open( &digest_handle, srp_hash_to_gcrypt( alg ), 0 );
+ gcry_md_write( digest_handle, n_n1_bytes, i_pad_length );
+ gcry_md_write( digest_handle, n_n2_bytes, i_pad_length );
+ p_hashed_bytes = gcry_md_read( digest_handle, srp_hash_to_gcrypt( alg ) );
+
+ gcry_mpi_t *ret = malloc( sizeof( *ret ) );
+ gcry_mpi_scan( ret, GCRYMPI_FMT_USG, p_hashed_bytes, srp_hash_length( alg ), NULL );
+ gcry_md_close( digest_handle );
+
+ free( n_n1_bytes );
+ free( n_n2_bytes );
+ return ret;
+}
+
+// H(n | bytes)
+static gcry_mpi_t *hash_mpi_concat_bytes( srp_hash_algorithm alg, gcry_mpi_t n, const uint8_t *p_bytes,
+ size_t i_len_bytes )
+{
+ uint8_t *p_hashed_bytes = NULL;
+ size_t i_len_n = mpi_nbytes( n );
+ uint8_t *p_n_bytes = calloc( i_len_n, sizeof( *p_n_bytes ) );
+
+ gcry_mpi_print( GCRYMPI_FMT_USG, p_n_bytes, i_len_n, NULL, n );
+
+ gcry_md_hd_t digest_handle;
+ gcry_md_open( &digest_handle, srp_hash_to_gcrypt( alg ), 0 );
+ gcry_md_write( digest_handle, p_n_bytes, i_len_n );
+ gcry_md_write( digest_handle, p_bytes, i_len_bytes );
+ p_hashed_bytes = gcry_md_read( digest_handle, srp_hash_to_gcrypt( alg ) );
+
+ gcry_mpi_t *ret = malloc( sizeof( *ret ) );
+ gcry_mpi_scan( ret, GCRYMPI_FMT_USG, p_hashed_bytes, srp_hash_length( alg ), NULL );
+ gcry_md_close( digest_handle );
+
+ free( p_n_bytes );
+ return ret;
+}
+
+// H(s | H(I | ":" | P))
+static gcry_mpi_t *calculate_x( srp_hash_algorithm alg, gcry_mpi_t salt,
+ const char *psz_username, size_t i_username_len,
+ const char *psz_password, size_t i_password_len )
+{
+ gcry_md_hd_t digest_handle;
+
+ gcry_md_open( &digest_handle, srp_hash_to_gcrypt( alg ), 0 );
+ gcry_md_write( digest_handle, psz_username, i_username_len );
+ gcry_md_write( digest_handle, ":", 1 );
+ gcry_md_write( digest_handle, psz_password, i_password_len );
+
+ gcry_mpi_t *ret = hash_mpi_concat_bytes(
+ alg, salt,
+ gcry_md_read( digest_handle, srp_hash_to_gcrypt( alg ) ),
+ gcry_md_get_algo_dlen( gcry_md_get_algo( digest_handle ) )
+ );
+
+ gcry_md_close( digest_handle );
+ return ret;
+}
+
+// updates the digest handle with the mpi in p_n
+static void md_write_mpi( gcry_md_hd_t p_digest_handle, gcry_mpi_t n )
+{
+ size_t len = mpi_nbytes( n );
+ uint8_t *p_bytes = calloc( len, sizeof( *p_bytes ) );
+
+ gcry_mpi_print( GCRYMPI_FMT_USG, p_bytes, len, NULL, n );
+ gcry_md_write( p_digest_handle, p_bytes, len );
+ free( p_bytes );
+}
+
+static void srp_calculate_K( srp_hash_algorithm alg, gcry_mpi_t S, uint8_t **pp_bytes_K, size_t *pi_length_K )
+{
+ return hash_mpi( alg, S, pp_bytes_K, pi_length_K );
+}
+
+// H(S ~ 00 00 00 00) ~ H(S ~ 00 00 00 01)
+static void apple_calculate_K( srp_hash_algorithm alg, gcry_mpi_t S, uint8_t **pp_bytes_K, size_t *pi_length_K )
+{
+ uint8_t first_bytes[4] = {0, 0, 0, 0};
+ uint8_t second_bytes[4] = {0, 0, 0, 1};
+ size_t i_hash_length = srp_hash_length( alg );
+ *pp_bytes_K = malloc( i_hash_length * 2 * sizeof( **pp_bytes_K ) );
+ if ( pi_length_K != NULL ) *pi_length_K = i_hash_length * 2;
+
+ gcry_mpi_t *p_K1 = hash_mpi_concat_bytes( alg, S, first_bytes, 4 );
+ gcry_mpi_t *p_K2 = hash_mpi_concat_bytes( alg, S, second_bytes, 4 );
+
+ uint8_t p_K1_bytes[i_hash_length];
+ uint8_t p_K2_bytes[i_hash_length];
+ gcry_mpi_print( GCRYMPI_FMT_USG, p_K1_bytes, i_hash_length, NULL, *p_K1 );
+ gcry_mpi_print( GCRYMPI_FMT_USG, p_K2_bytes, i_hash_length, NULL, *p_K2 );
+
+ memcpy( *pp_bytes_K, p_K1_bytes, i_hash_length );
+ memcpy( *pp_bytes_K + i_hash_length, p_K2_bytes, i_hash_length );
+
+ gcry_mpi_release( *p_K1 );
+ gcry_mpi_release( *p_K2 );
+}
+
+// S = ((B - k * (g^x % N)) ^ (a + u * x)) % N
+static void calculate_S( const struct srp_user *p_user, const gcry_mpi_t *B, const gcry_mpi_t *u, const gcry_mpi_t *x,
+ const gcry_mpi_t *k, gcry_mpi_t *S )
+{
+ gcry_mpi_t tmp1 = gcry_mpi_new( 128 );
+ gcry_mpi_t tmp2 = gcry_mpi_new( 128 );
+ gcry_mpi_t tmp3 = gcry_mpi_new( 128 );
+
+ gcry_mpi_mul( tmp1, *u, *x ); // u * x
+ gcry_mpi_add( tmp2, p_user->a, tmp1 ); // a + u * x
+ gcry_mpi_powm( tmp1, p_user->p_ng->g, *x, p_user->p_ng->N ); // g^x % N
+ gcry_mpi_mul( tmp3, *k, tmp1 ); // k * (g^x % N)
+ gcry_mpi_sub( tmp1, *B, tmp3 ); // B - k * (g^x % N)
+ gcry_mpi_powm( *S, tmp1, tmp2, p_user->p_ng->N ); // ((B - k * (g^x % N)) ^ (a + u * x)) % N
+
+ gcry_mpi_release( tmp1 );
+ gcry_mpi_release( tmp2 );
+ gcry_mpi_release( tmp3 );
+}
+
+// p_bytes_M1 = H(A | B | S)
+static void
+srp_calculate_M1( struct srp_user *p_user, gcry_mpi_t s, gcry_mpi_t B, uint8_t **pp_bytes_M1, size_t *pi_length_M1 )
+{
+ (void)( s );
+ gcry_md_hd_t digest_handle;
+ int gcrypt_alg = srp_hash_to_gcrypt( p_user->hash_alg );
+
+ gcry_md_open( &digest_handle, gcrypt_alg, 0 );
+ md_write_mpi( digest_handle, p_user->A );
+ md_write_mpi( digest_handle, B );
+ md_write_mpi( digest_handle, p_user->S );
+ const uint8_t *p_hashed = gcry_md_read( digest_handle, gcrypt_alg );
+
+ if ( pi_length_M1 != NULL )
+ {
+ *pi_length_M1 = srp_hash_length( p_user->hash_alg );
+ }
+ *pp_bytes_M1 = malloc( *pi_length_M1 * sizeof( **pp_bytes_M1 ) );
+ memcpy( *pp_bytes_M1, p_hashed, *pi_length_M1 );
+ gcry_md_close( digest_handle );
+}
+
+// p_bytes_M1 = H(H(N) xor H(g) ~ H(I) ~ s ~ A ~ B ~ K)
+static void
+apple_calculate_M1( struct srp_user *p_user, gcry_mpi_t s, gcry_mpi_t B, uint8_t **pp_bytes_M1, size_t *pi_length_M1 )
+{
+ size_t i_digest_length = srp_hash_length( p_user->hash_alg );
+ gcry_md_hd_t digest_handle;
+ uint8_t *H_N = NULL;
+ uint8_t *H_g = NULL;
+ uint8_t *H_I = NULL;
+ uint8_t *HN_xor_Hg = calloc( i_digest_length, sizeof( *HN_xor_Hg ) );
+
+ hash_mpi( p_user->hash_alg, p_user->p_ng->N, &H_N, NULL );
+ hash_mpi( p_user->hash_alg, p_user->p_ng->g, &H_g, NULL );
+ hash_bytes( p_user->hash_alg, (const uint8_t *)p_user->psz_username, strlen( p_user->psz_username ), &H_I );
+
+ for ( size_t i = 0; i < i_digest_length; i++ )
+ HN_xor_Hg[i] = H_N[i] ^ H_g[i];
+
+ int gcrypt_alg = srp_hash_to_gcrypt( p_user->hash_alg );
+
+ uint8_t *p_bytes_K;
+ srp_user_get_hashed_session_key( p_user, &p_bytes_K, NULL );
+
+ gcry_md_open( &digest_handle, gcrypt_alg, 0 );
+ gcry_md_write( digest_handle, HN_xor_Hg, i_digest_length );
+ gcry_md_write( digest_handle, H_I, i_digest_length );
+ md_write_mpi( digest_handle, s );
+ md_write_mpi( digest_handle, p_user->A );
+ md_write_mpi( digest_handle, B );
+ gcry_md_write( digest_handle, p_bytes_K, i_digest_length * 2 );
+ const uint8_t *p_hashed = gcry_md_read( digest_handle, gcrypt_alg );
+
+ if ( pi_length_M1 != NULL )
+ {
+ *pi_length_M1 = i_digest_length;
+ }
+ *pp_bytes_M1 = malloc( i_digest_length * sizeof( **pp_bytes_M1 ) );
+ memcpy( *pp_bytes_M1, p_hashed, i_digest_length );
+ gcry_md_close( digest_handle );
+
+ free( p_bytes_K );
+}
+
+// p_bytes_M2 = H(A ~ p_bytes_M1 ~ K)
+static void
+calculate_M2( srp_hash_algorithm alg, gcry_mpi_t A, const uint8_t *p_bytes_M1, const uint8_t *p_bytes_K,
+ uint8_t **pp_bytes_M2, size_t *pi_length_M2 )
+{
+ gcry_md_hd_t digest_handle;
+ size_t i_digest_length = srp_hash_length( alg );
+ int gcrypt_alg = srp_hash_to_gcrypt( alg );
+
+ gcry_md_open( &digest_handle, gcrypt_alg, 0 );
+ md_write_mpi( digest_handle, A );
+ gcry_md_write( digest_handle, p_bytes_M1, i_digest_length );
+ gcry_md_write( digest_handle, p_bytes_K, i_digest_length * 2 );
+ const uint8_t *p_hashed = gcry_md_read( digest_handle, gcrypt_alg );
+
+ if ( pi_length_M2 != NULL )
+ {
+ *pi_length_M2 = i_digest_length;
+ }
+ *pp_bytes_M2 = malloc( i_digest_length * sizeof( **pp_bytes_M2 ) );
+ memcpy( *pp_bytes_M2, p_hashed, i_digest_length );
+ gcry_md_close( digest_handle );
+}
+
+/***********************************************************************************************************
+ *
+ * Exported Functions
+ *
+ ***********************************************************************************************************/
+
+size_t srp_hash_length( srp_hash_algorithm alg )
+{
+ return gcry_md_get_algo_dlen( srp_hash_to_gcrypt( alg ) );
+}
+
+// Verifier code will be used for airplay receiving.
+// It currently is not being used and may be wrong.
+//
+//void srp_create_salted_verification_key( srp_hash_algorithm alg, srp_Ng_type Ng_type,
+// const char *psz_username, size_t i_length_username,
+// const char *psz_password, size_t i_length_password,
+// uint8_t **pp_bytes_s, size_t *pi_length_s,
+// uint8_t **pp_bytes_v, size_t *pi_length_v,
+// const char *psz_N_hex, const char *psz_g_hex )
+//{
+// gcry_mpi_t s = gcry_mpi_new( 256 );
+// gcry_mpi_t v = gcry_mpi_new( 256 );
+// gcry_mpi_t *x = NULL;
+// Ng_pair_t *ng = new_ng( Ng_type, psz_N_hex, psz_g_hex );
+//
+// if ( !s || !v || !ng )
+// goto cleanup_and_exit;
+//
+// gcry_mpi_randomize( s, 32, GCRY_STRONG_RANDOM );
+//
+// x = calculate_x( alg, s, psz_username, i_length_username, psz_password, i_length_password );
+//
+// gcry_mpi_powm( v, ng->g, *x, ng->N );
+//
+// *pi_length_s = mpi_nbytes( s );
+// *pi_length_v = mpi_nbytes( v );
+// *pp_bytes_s = malloc( *pi_length_s * sizeof( **pp_bytes_s ) );
+// *pp_bytes_v = malloc( *pi_length_v * sizeof( **pp_bytes_v ) );
+//
+// gcry_mpi_print( GCRYMPI_FMT_USG, *pp_bytes_s, *pi_length_s, NULL, s );
+// gcry_mpi_print( GCRYMPI_FMT_USG, *pp_bytes_v, *pi_length_v, NULL, v );
+//
+// cleanup_and_exit:
+// delete_ng( &ng );
+// gcry_mpi_release( s );
+// gcry_mpi_release( v );
+// gcry_mpi_release( *x );
+//}
+//
+//
+///* Out: p_bytes_B, len_B.
+// *
+// * On failure, p_bytes_B will be set to NULL and len_B will be set to 0
+// */
+//struct srp_verifier *srp_verifier_new( srp_hash_algorithm alg, srp_Ng_type Ng_type,
+// const char *psz_username, size_t i_length_username,
+// const uint8_t *p_bytes_s, size_t i_length_s,
+// const uint8_t *p_bytes_v, size_t i_length_v,
+// const uint8_t *p_bytes_A, size_t i_length_A,
+// uint8_t **pp_bytes_B, size_t *pi_length_B,
+// const char *psz_N_hex, const char *psz_g_hex )
+//{
+// gcry_mpi_t s;
+// gcry_mpi_t v;
+// gcry_mpi_t A;
+// gcry_mpi_t *u = NULL;
+// gcry_mpi_t *k = NULL;
+// gcry_mpi_t B = gcry_mpi_new( 128 );
+// gcry_mpi_t S = gcry_mpi_new( 128 );
+// gcry_mpi_t b = gcry_mpi_new( 128 );
+// gcry_mpi_t tmp1 = gcry_mpi_new( 128 );
+// gcry_mpi_t tmp2 = gcry_mpi_new( 128 );
+// Ng_pair_t *ng = new_ng( Ng_type, psz_N_hex, psz_g_hex );
+// struct srp_verifier *ver = NULL;
+//
+// gcry_mpi_scan( &s, GCRYMPI_FMT_USG, p_bytes_s, (size_t)i_length_s, NULL );
+// gcry_mpi_scan( &v, GCRYMPI_FMT_USG, p_bytes_v, (size_t)i_length_v, NULL );
+// gcry_mpi_scan( &A, GCRYMPI_FMT_USG, p_bytes_A, (size_t)i_length_A, NULL );
+//
+// *pi_length_B = 0;
+// *pp_bytes_B = 0;
+//
+// if ( !s || !v || !A || !B || !S || !b || !tmp1 || !tmp2 || !ng )
+// goto cleanup_and_exit;
+//
+// ver = calloc( 1, sizeof( ver ) );
+//
+// if ( !ver )
+// goto cleanup_and_exit;
+//
+// ver->hash_alg = alg;
+// ver->p_Ng_pair = ng;
+// ver->b_authenticated = false;
+//
+// ver->psz_username = malloc( ( i_length_username + 1 ) * sizeof( ver->psz_username ) );
+// memcpy( ver->psz_username, psz_username, ( i_length_username + 1 ) );
+//
+// /* SRP-6a safety check */
+// gcry_mpi_mod( tmp1, A, ng->N );
+// if ( gcry_mpi_cmp_ui( tmp1, 0 ) )
+// {
+// gcry_mpi_randomize( b, 256, GCRY_STRONG_RANDOM );
+// k = hash_mpi_concat_mpi( alg, ng->N, ng->g );
+//
+// /* B = (k * v + (g^b % N)) % N */
+// gcry_mpi_mul( tmp1, *k, v );
+// gcry_mpi_powm( tmp2, ng->g, b, ng->N );
+// gcry_mpi_addm( B, tmp1, tmp2, ng->N );
+// u = hash_mpi_concat_mpi( alg, A, B );
+//
+// /* S = (A *(v^u)) ^ b */
+// gcry_mpi_powm( tmp1, v, *u, ng->N );
+// gcry_mpi_mul( tmp2, A, tmp1 );
+// gcry_mpi_powm( S, tmp2, b, ng->N );
+//
+// hash_mpi( alg, S, &ver->p_bytes_K, NULL );
+//
+// // calculate p_bytes_M1 and p_bytes_M2
+// apple_calculate_M1( NULL, s, B, &ver->p_bytes_M1, NULL );
+// calculate_M2( alg, A, ver->p_bytes_M1, ver->p_bytes_K, &ver->p_bytes_M2, NULL );
+//
+// *pi_length_B = mpi_nbytes( B );
+// *pp_bytes_B = calloc( (size_t)*pi_length_B, sizeof( *pp_bytes_B ) );
+//
+// gcry_mpi_print( GCRYMPI_FMT_USG, *pp_bytes_B, *pi_length_B, NULL, B );
+//
+// ver->p_bytes_B = *pp_bytes_B;
+// }
+//
+// cleanup_and_exit:
+// gcry_mpi_release( s );
+// gcry_mpi_release( v );
+// gcry_mpi_release( A );
+// if ( u != NULL ) gcry_mpi_release( *u );
+// if ( k != NULL ) gcry_mpi_release( *k );
+// gcry_mpi_release( B );
+// gcry_mpi_release( S );
+// gcry_mpi_release( b );
+// gcry_mpi_release( tmp1 );
+// gcry_mpi_release( tmp2 );
+// return ver;
+//}
+//
+//void srp_verifier_delete( struct srp_verifier **p_verifier )
+//{
+// if ( p_verifier != NULL )
+// {
+// delete_ng( &( *p_verifier )->p_Ng_pair );
+// free( ( *p_verifier )->psz_username );
+// free( ( *p_verifier )->p_bytes_B );
+// free( *p_verifier );
+// *p_verifier = NULL;
+// }
+//}
+//
+//
+//bool srp_verifier_is_authenticated( struct srp_verifier *p_verifier )
+//{
+// return p_verifier->b_authenticated;
+//}
+//
+//
+//const char *srp_verifier_get_username( struct srp_verifier *p_verifier )
+//{
+// return p_verifier->psz_username;
+//}
+//
+//void srp_verifier_verify_session( struct srp_verifier *p_verifier, const uint8_t *p_bytes_M1, size_t i_length_M1,
+// const uint8_t **pp_bytes_M2, size_t *pi_length_M2 )
+//{
+// assert( i_length_M1 == srp_verifier_get_session_key_length( p_verifier ) );
+// if ( memcmp( p_verifier->p_bytes_M1, p_bytes_M1, srp_verifier_get_session_key_length( p_verifier ) ) == 0 )
+// {
+// p_verifier->b_authenticated = true;
+// *pp_bytes_M2 = p_verifier->p_bytes_M2;
+// *pi_length_M2 = i_length_M1;
+// } else
+// {
+// *pp_bytes_M2 = NULL;
+// }
+//}
+//
+//const uint8_t *srp_verifier_get_session_key( struct srp_verifier *p_verifier, size_t *key_length )
+//{
+// if ( key_length )
+// *key_length = srp_verifier_get_session_key_length( p_verifier );
+// return p_verifier->p_bytes_K;
+//}
+//
+//size_t srp_verifier_get_session_key_length( struct srp_verifier *p_verifier )
+//{
+// return srp_hash_length( p_verifier->hash_alg );
+//}
+
+/*******************************************************************************/
+
+// initializes the user
+struct srp_user *_srp_user_new( srp_hash_algorithm alg, srp_Ng_type ng_type,
+ const char *psz_username, size_t i_len_username,
+ const char *psz_password, size_t i_len_password,
+ const char *psz_n_hex, const char *psz_g_hex,
+ void (*fp_calculate_K)( srp_hash_algorithm alg, gcry_mpi_t S, uint8_t **pp_bytes_K,
+ size_t *pi_length_K ),
+ void (*fp_calculate_M1)( struct srp_user *p_user, gcry_mpi_t s, gcry_mpi_t B,
+ uint8_t **pp_bytes_M1, size_t *pi_length_M1 ) )
+{
+ assert( fp_calculate_K != NULL );
+ assert( fp_calculate_M1 != NULL );
+
+ struct srp_user *usr = calloc( 1, sizeof( *usr ) );
+
+ if ( !usr )
+ goto err_exit;
+
+ usr->hash_alg = alg;
+ usr->p_ng = new_ng( ng_type, psz_n_hex, psz_g_hex );
+
+ usr->a = gcry_mpi_new( 256 );
+ usr->A = gcry_mpi_new( 128 );
+ usr->S = gcry_mpi_new( 128 );
+
+ if ( !usr->p_ng || !usr->a || !usr->A || !usr->S )
+ goto err_exit;
+
+ usr->psz_username = calloc( i_len_username + 1, sizeof( usr->psz_username ) );
+ usr->psz_password = calloc( i_len_password + 1, sizeof( usr->psz_password ) );
+ usr->i_password_len = i_len_password;
+ usr->i_username_len = i_len_username;
+
+ if ( !usr->psz_username || !usr->psz_password )
+ goto err_exit;
+
+ strncpy( usr->psz_username, psz_username, i_len_username + 1 );
+ strncpy( usr->psz_password, psz_password, i_len_password + 1 );
+
+ usr->b_authenticated = false;
+
+ usr->p_bytes_A = NULL;
+
+ usr->fp_calculate_K = fp_calculate_K;
+ usr->fp_calculate_M1 = fp_calculate_M1;
+
+ return usr;
+
+ err_exit:
+ if ( usr )
+ {
+ gcry_mpi_release( usr->a );
+ gcry_mpi_release( usr->A );
+ gcry_mpi_release( usr->S );
+ if ( usr->psz_username )
+ free( (void *)usr->psz_username );
+ if ( usr->psz_password )
+ {
+ memset( (void *)usr->psz_password, 0, usr->i_password_len );
+ free( (void *)usr->psz_password );
+ }
+ free( usr );
+ }
+
+ return NULL;
+}
+
+struct srp_user *srp_user_new( srp_hash_algorithm alg, srp_Ng_type Ng_type,
+ const char *psz_username, size_t i_length_username,
+ const char *psz_password, size_t i_length_password,
+ const char *psz_N_hex, const char *psz_g_hex )
+{
+ return _srp_user_new( alg, Ng_type, psz_username, i_length_username, psz_password, i_length_password, psz_N_hex,
+ psz_g_hex, srp_calculate_K, srp_calculate_M1 );
+}
+
+struct srp_user *apple_srp_user_new( const char *psz_username, size_t i_len_username,
+ const char *psz_password, size_t i_len_password )
+{
+ return _srp_user_new( SRP_SHA1, SRP_NG_2048, psz_username, i_len_username, psz_password, i_len_password, NULL, NULL,
+ apple_calculate_K, apple_calculate_M1 );
+}
+
+
+void srp_user_delete( struct srp_user *usr )
+{
+ if ( usr )
+ {
+ gcry_mpi_release( usr->a );
+ gcry_mpi_release( usr->A );
+ gcry_mpi_release( usr->S );
+
+ delete_ng( &usr->p_ng );
+
+ memset( (void *)usr->psz_password, 0, usr->i_password_len );
+
+ free( (char *)usr->psz_username );
+ free( (char *)usr->psz_password );
+
+ if ( usr->p_bytes_A )
+ free( (char *)usr->p_bytes_A );
+
+ memset( usr, 0, sizeof( *usr ) );
+ free( usr );
+ }
+}
+
+
+bool srp_user_is_authenticated( struct srp_user *p_user )
+{
+ return p_user->b_authenticated;
+}
+
+
+const char *srp_user_get_username( struct srp_user *usr )
+{
+ return usr->psz_username;
+}
+
+size_t srp_user_get_session_key_length( struct srp_user *p_user )
+{
+ return srp_hash_length( p_user->hash_alg );
+}
+
+void srp_user_get_hashed_session_key( const struct srp_user *p_user, uint8_t **pp_bytes_key, size_t *pi_length_k )
+{
+ size_t i_length_K = p_user->i_length_K;
+ uint8_t *p_bytes_K = malloc( i_length_K * sizeof( *p_bytes_K ) );
+ memcpy( p_bytes_K, p_user->p_bytes_K, i_length_K * sizeof( *p_bytes_K ) );
+
+ if ( pi_length_k )
+ *pi_length_k = i_length_K;
+
+ if ( pp_bytes_key )
+ *pp_bytes_key = p_bytes_K;
+}
+
+// generates a private key, and the corresponding public key A = g^a % N
+void srp_user_start_authentication( struct srp_user *p_user, const gcry_mpi_t *p_a, uint8_t **pp_bytes_A,
+ size_t *p_length_A )
+{
+ // A = g ^ a % N
+ if ( p_a != NULL )
+ {
+ p_user->a = *p_a;
+ } else
+ {
+ gcry_mpi_randomize( p_user->a, 256, GCRY_STRONG_RANDOM );
+ }
+
+ gcry_mpi_powm( p_user->A, p_user->p_ng->g, p_user->a, p_user->p_ng->N );
+
+ *p_length_A = mpi_nbytes( p_user->A );
+ *pp_bytes_A = malloc( *p_length_A * sizeof( **pp_bytes_A ) );
+ gcry_mpi_print( GCRYMPI_FMT_USG, *pp_bytes_A, *p_length_A, NULL, p_user->A );
+
+ p_user->p_bytes_A = *pp_bytes_A;
+}
+
+/* Output: bytes_M. Buffer length is SHA512_DIGEST_LENGTH */
+void srp_user_process_challenge( struct srp_user *p_user,
+ const uint8_t *p_bytes_s, size_t i_length_s,
+ const uint8_t *p_bytes_B, size_t i_length_B,
+ uint8_t **pp_bytes_M1, size_t *pi_length_M1 )
+{
+ gcry_mpi_t s;
+ gcry_mpi_t B;
+ gcry_mpi_t *u = NULL;
+ gcry_mpi_t *x = NULL;
+ gcry_mpi_t *k = NULL;
+
+ gcry_mpi_scan( &s, GCRYMPI_FMT_USG, p_bytes_s, i_length_s, NULL );
+ gcry_mpi_scan( &B, GCRYMPI_FMT_USG, p_bytes_B, i_length_B, NULL );
+
+ if ( !s || !B )
+ goto cleanup_and_exit;
+
+ u = hash_mpi_concat_mpi( p_user->hash_alg, p_user->A, B );
+
+ x = calculate_x( p_user->hash_alg, s, p_user->psz_username, p_user->i_username_len, p_user->psz_password,
+ p_user->i_password_len );
+
+ k = hash_mpi_concat_mpi_padded( p_user->hash_alg, mpi_nbytes( p_user->p_ng->N ), p_user->p_ng->N,
+ p_user->p_ng->g );
+
+ /* SRP-6a safety check */
+ if ( gcry_mpi_cmp_ui( B, 0 ) == 1 && gcry_mpi_cmp_ui( *u, 0 ) == 1 )
+ {
+
+ calculate_S( p_user, &B, u, x, k, &p_user->S );
+
+ p_user->fp_calculate_K( p_user->hash_alg, p_user->S, &p_user->p_bytes_K, &p_user->i_length_K );
+ p_user->fp_calculate_M1( p_user, s, B, &p_user->p_bytes_M1, NULL );
+
+ calculate_M2( p_user->hash_alg, p_user->A, p_user->p_bytes_M1, p_user->p_bytes_K, &p_user->p_bytes_M2, NULL );
+
+ *pp_bytes_M1 = p_user->p_bytes_M1;
+ *pi_length_M1 = srp_hash_length( p_user->hash_alg );
+ }
+
+ cleanup_and_exit:
+ gcry_mpi_release( s );
+ gcry_mpi_release( B );
+ if ( u != NULL ) gcry_mpi_release( *u );
+ if ( x != NULL ) gcry_mpi_release( *x );
+ if ( k != NULL ) gcry_mpi_release( *k );
+}
+
+bool srp_user_verify_session( struct srp_user *p_user, const uint8_t *p_bytes_M2 )
+{
+ if ( memcmp( p_user->p_bytes_M2, p_bytes_M2, srp_hash_length( p_user->hash_alg ) ) == 0 )
+ p_user->b_authenticated = 1;
+
+ return srp_user_is_authenticated( p_user );
+}
diff --git a/modules/stream_out/airplay/csrp/srp.h b/modules/stream_out/airplay/csrp/srp.h
new file mode 100755
index 0000000000..aaf90a32e9
--- /dev/null
+++ b/modules/stream_out/airplay/csrp/srp.h
@@ -0,0 +1,233 @@
+/*
+ * A Secure Remote Password 6a implementation (RFC 5054)
+ * Also includes Apple-Flavoured SRP which changes a
+ * few of the functions to be compatible with Apple TVs.
+ *
+ * Adapted from CSRP by Tom Cocagne by Alexander Lyon.
+ * https://github.com/cocagne/csrp
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2013 Tom Cocagne, Alexander Lyon
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is furnished to do
+ * so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+/*
+ * Purpose: This is an implementation of Secure Remote Password protocol version 6a
+ * as described in RFC5054 as well as "Apple Flavoured" SRP which is detailed
+ * here: https://code.videolan.org/GSoC2018/arlyon/vlc/wikis/AirPlay%201/SRP6
+ *
+ * Original Author: tom.cocagne at gmail.com (Tom Cocagne)
+ * Adapted By: arlyon at me.com (Alexander Lyon)
+ *
+ * Dependencies: libgcrypt
+ *
+ * Usage: Refer to test_srp.c for a demonstration
+ *
+ * Notes:
+ * This library allows multiple combinations of hashing algorithms and
+ * prime number constants. For authentication to succeed, the hash and
+ * prime number constants must match between
+ * srp_create_salted_verification_key(), srp_user_new(),
+ * and srp_verifier_new(). A recommended approach is to determine the
+ * desired level of security for an application and globally define the
+ * hash and prime number constants to the predetermined values.
+ *
+ * As one might suspect, more bits means more security. As one might also
+ * suspect, more bits also means more processing time. The test_srp.c
+ * program can be easily modified to profile various combinations of
+ * hash & prime number pairings.
+ */
+
+#ifndef SRP_H
+#define SRP_H
+
+struct srp_verifier;
+struct srp_user;
+
+typedef enum
+{
+ SRP_NG_1024,
+ SRP_NG_2048,
+ SRP_NG_4096,
+ SRP_NG_8192,
+ SRP_NG_CUSTOM
+} srp_Ng_type;
+
+typedef enum
+{
+ SRP_SHA1,
+ SRP_SHA224,
+ SRP_SHA256,
+ SRP_SHA384,
+ SRP_SHA512
+} srp_hash_algorithm;
+
+/*****************************************************************************
+ * Util
+ *****************************************************************************/
+
+/**
+ * Gets the hash length for a given SRP hash algorithm.
+ *
+ * @param alg The requested algorithm.
+ * @return The hash length.
+ */
+size_t srp_hash_length( srp_hash_algorithm alg );
+
+/**
+ * Creates the salted verification key for the given username and password.
+ *
+ * @param[out] pp_bytes_s A pointer to the client salt.
+ * @param[out] pp_bytes_v A pointer to the password verifier.
+ * @param psz_N_hex Optional when using SRP_NG_CUSTOM to define own prime.
+ * @param psz_g_hex Optional when using SRP_NG_CUSTOM to define own generator.
+ *
+ * @note: Freeing of pp_bytes_s, len_s, bytes_v and len_v is the responsibility of the user.
+ * @note: The n_hex and g_hex parameters should be NULL unless SRP_NG_CUSTOM is used for ng_type.
+ * If provided, they must contain a number in ASCII hexadecimal notation.
+ */
+void srp_create_salted_verification_key( srp_hash_algorithm alg, srp_Ng_type Ng_type,
+ const char *psz_username, size_t i_length_username,
+ const char *psz_password, size_t i_length_password,
+ uint8_t **pp_bytes_s, size_t *pi_length_s,
+ uint8_t **pp_bytes_v, size_t *pi_length_v,
+ const char *psz_N_hex, const char *psz_g_hex );
+
+/*****************************************************************************
+ * Verifier
+ *****************************************************************************/
+
+/**
+ * Creates a new SRPVerifier and generates the second public ephemeral value.
+ *
+ * @param[out] pp_bytes_B A pointer to the server ephemeral public key.
+ * @param psz_N_hex Optional when using SRP_NG_CUSTOM to define own prime.
+ * @param psz_g_hex Optional when using SRP_NG_CUSTOM to define own generator.
+ *
+ * @note: Freeing of bytes_B and the return value is the responsibility of the user.
+ * @note: The n_hex and g_hex parameters should be NULL unless SRP_NG_CUSTOM is used for ng_type.
+ * If provided, they must contain a number in ASCII hexadecimal notation.
+ */
+struct srp_verifier *srp_verifier_new( srp_hash_algorithm alg, srp_Ng_type Ng_type,
+ const char *psz_username, size_t i_length_username,
+ const uint8_t *p_bytes_s, size_t i_length_s,
+ const uint8_t *p_bytes_v, size_t i_length_v,
+ const uint8_t *p_bytes_A, size_t i_length_A,
+ uint8_t **pp_bytes_B, size_t *pi_length_B,
+ const char *psz_N_hex, const char *psz_g_hex );
+
+/**
+ * Checks if the verifier session key is equal to the user session key.
+ *
+ * @param[out] pp_bytes_M2 A pointer to the M2 value.
+ */
+void srp_verifier_verify_session( struct srp_verifier *p_verifier, const uint8_t *p_bytes_M1, size_t i_length_M1,
+ const uint8_t **pp_bytes_M2, size_t *pi_length_M2 );
+
+void srp_verifier_delete( struct srp_verifier **p_verifier );
+
+bool srp_verifier_is_authenticated( struct srp_verifier *p_verifier );
+
+const char *srp_verifier_get_username( struct srp_verifier *p_verifier );
+
+/**
+ * @param[out] key_length The length of the session key. Can be NULL, in which case it is ignored.
+ * @return The session key.
+ */
+const uint8_t *srp_verifier_get_session_key( struct srp_verifier *p_verifier, size_t *key_length );
+
+size_t srp_verifier_get_session_key_length( struct srp_verifier *p_verifier );
+
+/*****************************************************************************
+ * User
+ *****************************************************************************/
+
+/**
+ * Creates a new srp user.
+ *
+ * @param n_hex Optional when using SRP_NG_CUSTOM to define own prime.
+ * @param g_hex Optional when using SRP_NG_CUSTOM to define own generator.
+ *
+ * @note: Freeing of the return value is the responsibility of the user.
+ * @note: The n_hex and g_hex parameters should be NULL unless SRP_NG_CUSTOM is used for ng_type.
+ * If provided, they must contain a number in ASCII hexadecimal notation.
+ */
+struct srp_user *srp_user_new( srp_hash_algorithm alg, srp_Ng_type Ng_type,
+ const char *psz_username, size_t i_length_username,
+ const char *psz_password, size_t i_length_password,
+ const char *psz_N_hex, const char *psz_g_hex );
+
+/**
+ * Creates an instance of an apple flavoured SRP user.
+ *
+ * @see srp_user_new
+ */
+struct srp_user *apple_srp_user_new( const char *psz_username, size_t i_len_username,
+ const char *psz_password, size_t i_len_password );
+
+/**
+ * Generates the first ephemeral public key and username from the supplied SRPUser.
+ *
+ * @param p_a If non-null, a is used as the private key.
+ * @param[out] pp_bytes_A A pointer to the client ephemeral public key.
+ *
+ * @note: Freeing of bytes_A is the responsibility of the user.
+ */
+void srp_user_start_authentication( struct srp_user *p_user, const gcry_mpi_t *p_a,
+ uint8_t **pp_bytes_A, size_t *p_length_A );
+
+/**
+ * Calculates M1 from s and B.
+ *
+ * @param[out] pp_bytes_M1 The session key verifier.
+ *
+ * @note: Freeing of bytes_M1 is the responsibility of the user.
+ */
+void srp_user_process_challenge( struct srp_user *p_user,
+ const uint8_t *p_bytes_s, size_t i_length_s,
+ const uint8_t *p_bytes_B, size_t i_length_B,
+ uint8_t **pp_bytes_M1, size_t *pi_length_M1 );
+
+/**
+ * Authenticates the given SRPUser if the supplied M2 (from the verifier) is the same as the one of the user.
+ * @param p_user The user to verify.
+ * @param p_bytes_M2 The M2 from the server.
+ * @return true if the user is authenticated as a result otherwise false.
+ *
+ * @note p_bytes_M2 must be exactly srp_user_get_session_key_length() bytes in size
+ */
+bool srp_user_verify_session( struct srp_user *p_user, const uint8_t *p_bytes_M2 );
+
+void srp_user_delete( struct srp_user *usr );
+
+bool srp_user_is_authenticated( struct srp_user *p_user );
+
+const char *srp_user_get_username( struct srp_user *usr );
+
+/**
+ * @param[out] pi_length_k The length of the session key. Can be NULL, in which case it is ignored.
+ * @return The session key. This memory must be free'd.
+ */
+void srp_user_get_hashed_session_key( const struct srp_user *p_user, uint8_t **pp_bytes_key, size_t *pi_length_k );
+
+size_t srp_user_get_session_key_length( struct srp_user *p_user );
+
+#endif
--
2.19.0
More information about the vlc-devel
mailing list