From 8368ca76867eb5e3bb59b90c8dae5d685a171430 Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Fri, 27 Nov 2020 22:46:38 +0100 Subject: [PATCH] [airplay] Add support for AirPlay 2 Includes - Implementation in src/outputs/airplays2, type OUTPUT_TYPE_AIRPLAY - Homekit pairing, both normal (with PIN) and transient - New session startup sequence, incl GET /info, SETPEERS and 2 x SETUP - No more OPTIONS and ANNOUNCE - Use POST /feedback for keepalive instead of SET_PARAMETERS - Sequence dispatching instead of callback chains - Continue despite "Bad request" to SET_PARAMETER (volume) - Opening of event connection to receiver (reverse rtsp connection) Still to be done - Password authentication - Handling of events --- src/Makefile.am | 4 +- src/evrtsp/evrtsp.h | 9 +- src/evrtsp/rtsp-internal.h | 3 + src/evrtsp/rtsp.c | 15 + src/outputs.c | 7 + src/outputs.h | 1 + src/outputs/airplay.c | 4429 +++++++++++++++++ src/outputs/pair-internal.h | 240 + src/outputs/pair.c | 578 +++ src/outputs/pair.h | 117 + .../{raop_verification.c => pair_fruit.c} | 613 +-- src/outputs/pair_homekit.c | 1885 +++++++ src/outputs/raop.c | 133 +- src/outputs/raop_verification.h | 66 - 14 files changed, 7479 insertions(+), 621 deletions(-) create mode 100644 src/outputs/airplay.c create mode 100644 src/outputs/pair-internal.h create mode 100644 src/outputs/pair.c create mode 100644 src/outputs/pair.h rename src/outputs/{raop_verification.c => pair_fruit.c} (63%) create mode 100644 src/outputs/pair_homekit.c delete mode 100644 src/outputs/raop_verification.h diff --git a/src/Makefile.am b/src/Makefile.am index e290ee06..f061f0ff 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -26,7 +26,8 @@ MPD_SRC=mpd.c mpd.h endif if COND_RAOP_VERIFICATION -RAOP_VERIFICATION_SRC=outputs/raop_verification.c outputs/raop_verification.h +RAOP_VERIFICATION_SRC=outputs/pair_fruit.c outputs/pair_homekit.c outputs/pair.c outputs/pair-internal.h outputs/pair.h +AM_CPPFLAGS += -DCONFIG_GCRYPT endif if COND_ALSA @@ -134,6 +135,7 @@ forked_daapd_SOURCES = main.c \ outputs.h outputs.c \ outputs/rtp_common.h outputs/rtp_common.c \ outputs/raop.c $(RAOP_VERIFICATION_SRC) \ + outputs/airplay.c \ outputs/streaming.c outputs/dummy.c outputs/fifo.c \ $(ALSA_SRC) $(PULSEAUDIO_SRC) $(CHROMECAST_SRC) \ evrtsp/rtsp.c evrtsp/evrtsp.h evrtsp/rtsp-internal.h evrtsp/log.h \ diff --git a/src/evrtsp/evrtsp.h b/src/evrtsp/evrtsp.h index 330eec11..f8253aa6 100644 --- a/src/evrtsp/evrtsp.h +++ b/src/evrtsp/evrtsp.h @@ -45,8 +45,9 @@ extern "C" { /* Response codes */ #define RTSP_OK 200 -#define RTSP_UNAUTHORIZED 401 -#define RTSP_FORBIDDEN 403 +#define RTSP_UNAUTHORIZED 401 +#define RTSP_FORBIDDEN 403 +#define RTSP_CONNECTION_AUTH_REQUIRED 470 struct evrtsp_connection; @@ -135,6 +136,10 @@ void evrtsp_connection_free(struct evrtsp_connection *evcon); void evrtsp_connection_set_closecb(struct evrtsp_connection *evcon, void (*)(struct evrtsp_connection *, void *), void *); +/** Set a callback for encryption/decryption. */ +void evrtsp_connection_set_ciphercb(struct evrtsp_connection *evcon, + void (*)(struct evbuffer *, void *, int encrypt), void *); + /** * Associates an event base with the connection - can only be called * on a freshly created connection object that has not been used yet. diff --git a/src/evrtsp/rtsp-internal.h b/src/evrtsp/rtsp-internal.h index 5a6a3610..957f3c33 100644 --- a/src/evrtsp/rtsp-internal.h +++ b/src/evrtsp/rtsp-internal.h @@ -85,6 +85,9 @@ struct evrtsp_connection { void (*closecb)(struct evrtsp_connection *, void *); void *closecb_arg; + void (*ciphercb)(struct evbuffer *evbuf, void *, int encrypt); + void *ciphercb_arg; + struct event_base *base; }; diff --git a/src/evrtsp/rtsp.c b/src/evrtsp/rtsp.c index 692e0596..0f4ff376 100644 --- a/src/evrtsp/rtsp.c +++ b/src/evrtsp/rtsp.c @@ -620,6 +620,9 @@ evrtsp_read(int fd, short what, void *arg) return; } + if (evcon->ciphercb) + evcon->ciphercb(evcon->input_buffer, evcon->ciphercb_arg, 0); + switch (evcon->state) { case EVCON_READING_FIRSTLINE: evrtsp_read_firstline(evcon, req); @@ -725,6 +728,10 @@ evrtsp_request_dispatch(struct evrtsp_connection* evcon) /* Create the header from the store arguments */ evrtsp_make_header(evcon, req); + /* forked-daapd customisation for encryption */ + if (evcon->ciphercb) + evcon->ciphercb(evcon->output_buffer, evcon->ciphercb_arg, 1); + evrtsp_write_buffer(evcon, evrtsp_write_connectioncb, NULL); } @@ -1312,6 +1319,14 @@ evrtsp_connection_set_closecb(struct evrtsp_connection *evcon, evcon->closecb_arg = cbarg; } +void +evrtsp_connection_set_ciphercb(struct evrtsp_connection *evcon, + void (*cb)(struct evbuffer *, void *, int encrypt), void *cbarg) +{ + evcon->ciphercb = cb; + evcon->ciphercb_arg = cbarg; +} + void evrtsp_connection_get_local_address(struct evrtsp_connection *evcon, char **address, u_short *port, int *family) diff --git a/src/outputs.c b/src/outputs.c index 596fcfc4..ab12921c 100644 --- a/src/outputs.c +++ b/src/outputs.c @@ -40,6 +40,7 @@ #include "outputs.h" extern struct output_definition output_raop; +extern struct output_definition output_airplay; extern struct output_definition output_streaming; extern struct output_definition output_dummy; extern struct output_definition output_fifo; @@ -59,6 +60,7 @@ extern struct event_base *evbase_player; // Must be in sync with enum output_types static struct output_definition *outputs[] = { &output_raop, + &output_airplay, &output_streaming, &output_dummy, &output_fifo, @@ -1226,6 +1228,11 @@ outputs_init(void) return -1; } + if (outputs[i]->disabled) + { + continue; + } + if (!outputs[i]->init) { no_output = 0; diff --git a/src/outputs.h b/src/outputs.h index 3d452601..9a9f8a53 100644 --- a/src/outputs.h +++ b/src/outputs.h @@ -59,6 +59,7 @@ typedef int (*output_metadata_finalize_cb)(struct output_metadata *metadata); enum output_types { OUTPUT_TYPE_RAOP, + OUTPUT_TYPE_AIRPLAY, OUTPUT_TYPE_STREAMING, OUTPUT_TYPE_DUMMY, OUTPUT_TYPE_FIFO, diff --git a/src/outputs/airplay.c b/src/outputs/airplay.c new file mode 100644 index 00000000..c4d8baa1 --- /dev/null +++ b/src/outputs/airplay.c @@ -0,0 +1,4429 @@ +/* + * ALAC encoding adapted from airplay_play + * Copyright (C) 2005 Shiro Ninomiya + * GPLv2+ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include "plist_wrap.h" + +#include "evrtsp/evrtsp.h" +#include "conffile.h" +#include "logger.h" +#include "mdns.h" +#include "misc.h" +#include "player.h" +#include "db.h" +#include "artwork.h" +#include "dmap_common.h" +#include "rtp_common.h" +#include "outputs.h" + +#include "pair.h" + +// Airplay 2 has a gazallion parameters, many of them unknown to us. With the +// below it is possible to easily try different variations. +#define AIRPLAY_USE_STREAMID 0 +#define AIRPLAY_USE_PAIRING_TRANSIENT 1 + + +#define ALAC_HEADER_LEN 3 + +#define RAOP_QUALITY_SAMPLE_RATE_DEFAULT 44100 +#define RAOP_QUALITY_BITS_PER_SAMPLE_DEFAULT 16 +#define RAOP_QUALITY_CHANNELS_DEFAULT 2 + +// AirTunes v2 number of samples per packet +// Probably using this value because 44100/352 and 48000/352 has good 32 byte +// alignment, which improves performance of some encoders +#define RAOP_SAMPLES_PER_PACKET 352 + +#define RAOP_RTP_PAYLOADTYPE 0x60 + +// How many RTP packets keep in a buffer for retransmission +#define RAOP_PACKET_BUFFER_SIZE 1000 + +#define RAOP_MD_DELAY_STARTUP 15360 +#define RAOP_MD_DELAY_SWITCH (RAOP_MD_DELAY_STARTUP * 2) +#define RAOP_MD_WANTS_TEXT (1 << 0) +#define RAOP_MD_WANTS_ARTWORK (1 << 1) +#define RAOP_MD_WANTS_PROGRESS (1 << 2) + +// ATV4 and Homepod disconnect for reasons that are not clear, but sending them +// progress metadata at regular intervals reduces the problem. The below +// interval was determined via testing, see: +// https://github.com/ejurgensen/forked-daapd/issues/734#issuecomment-622959334 +#define RAOP_KEEP_ALIVE_INTERVAL 25 + +// This is an arbitrary value which just needs to be kept in sync with the config +#define RAOP_CONFIG_MAX_VOLUME 11 + +const char *pair_device_id = "AABBCCDD11223344"; // TODO use actual ID + +union sockaddr_all +{ + struct sockaddr_in sin; + struct sockaddr_in6 sin6; + struct sockaddr sa; + struct sockaddr_storage ss; +}; + +enum airplay_devtype { + RAOP_DEV_APEX1_80211G, + RAOP_DEV_APEX2_80211N, + RAOP_DEV_APEX3_80211N, + RAOP_DEV_APPLETV, + RAOP_DEV_APPLETV4, + RAOP_DEV_HOMEPOD, + RAOP_DEV_OTHER, +}; + +// Session is starting up +#define AIRPLAY_STATE_F_STARTUP (1 << 13) +// Streaming is up (connection established) +#define AIRPLAY_STATE_F_CONNECTED (1 << 14) +// Couldn't start device +#define AIRPLAY_STATE_F_FAILED (1 << 15) + +enum airplay_state { + // Device is stopped (no session) + AIRPLAY_STATE_STOPPED = 0, + // Session startup + AIRPLAY_STATE_STARTUP = AIRPLAY_STATE_F_STARTUP | 0x01, + AIRPLAY_STATE_OPTIONS = AIRPLAY_STATE_F_STARTUP | 0x02, + AIRPLAY_STATE_ANNOUNCE = AIRPLAY_STATE_F_STARTUP | 0x03, + AIRPLAY_STATE_SETUP = AIRPLAY_STATE_F_STARTUP | 0x04, + AIRPLAY_STATE_RECORD = AIRPLAY_STATE_F_STARTUP | 0x05, + // Session established + // - streaming ready (RECORD sent and acked, connection established) + // - commands (SET_PARAMETER) are possible + AIRPLAY_STATE_CONNECTED = AIRPLAY_STATE_F_CONNECTED | 0x01, + // Media data is being sent + AIRPLAY_STATE_STREAMING = AIRPLAY_STATE_F_CONNECTED | 0x02, + // Session teardown in progress (-> going to STOPPED state) + AIRPLAY_STATE_TEARDOWN = AIRPLAY_STATE_F_CONNECTED | 0x03, + // Session is failed, couldn't startup or error occurred + AIRPLAY_STATE_FAILED = AIRPLAY_STATE_F_FAILED | 0x01, + // Password issue: unknown password or bad password, or pending PIN from user + AIRPLAY_STATE_PASSWORD = AIRPLAY_STATE_F_FAILED | 0x02, +}; + +enum airplay_seq_type +{ + AIRPLAY_SEQ_ABORT = -1, + AIRPLAY_SEQ_START, + AIRPLAY_SEQ_START_RERUN, + AIRPLAY_SEQ_START_AP2, + AIRPLAY_SEQ_PROBE, + AIRPLAY_SEQ_FLUSH, + AIRPLAY_SEQ_STOP, + AIRPLAY_SEQ_FAILURE, + AIRPLAY_SEQ_PIN_START, + AIRPLAY_SEQ_SEND_VOLUME, + AIRPLAY_SEQ_SEND_TEXT, + AIRPLAY_SEQ_SEND_PROGRESS, + AIRPLAY_SEQ_SEND_ARTWORK, + AIRPLAY_SEQ_PAIR_SETUP, + AIRPLAY_SEQ_PAIR_VERIFY, + AIRPLAY_SEQ_PAIR_TRANSIENT, + AIRPLAY_SEQ_FEEDBACK, + AIRPLAY_SEQ_CONTINUE, // Must be last element +}; + +// Info about the device, which is not required by the player, only internally +struct airplay_extra +{ + enum airplay_devtype devtype; + + uint16_t wanted_metadata; + bool encrypt; + bool supports_auth_setup; + bool supports_pairing_transient; +}; + +struct airplay_master_session +{ + struct evbuffer *evbuf; + int evbuf_samples; + + struct rtp_session *rtp_session; + + struct rtcp_timestamp cur_stamp; + + uint8_t *rawbuf; + size_t rawbuf_size; + int samples_per_packet; + bool encrypt; + + // Number of samples that we tell the output to buffer (this will mean that + // the position that we send in the sync packages are offset by this amount + // compared to the rtptimes of the corresponding RTP packages we are sending) + int output_buffer_samples; + + struct airplay_master_session *next; +}; + +struct airplay_session +{ + uint64_t device_id; + int callback_id; + + struct airplay_master_session *master_session; + + struct evrtsp_connection *ctrl; + struct evrtsp_connection *event; + + enum airplay_state state; + + enum airplay_seq_type next_seq; + + uint16_t wanted_metadata; + bool req_has_auth; + bool encrypt; + bool auth_quirk_itunes; + bool supports_post; + bool supports_auth_setup; + + struct event *deferredev; + + int reqs_in_flight; + int cseq; + char *session; + uint32_t session_id; + char session_url[128]; + + char *realm; + char *nonce; + const char *password; + + char *devname; + char *address; + int family; + + int volume; + + char *local_address; + unsigned short data_port; + unsigned short control_port; + unsigned short events_port; + unsigned short timing_port; // ATV4 has this set to 0, but it is not used by forked-daapd anyway + + /* Pairing, see pair.h */ + enum pair_type pair_type; + struct pair_cipher_context *control_cipher_ctx; + struct pair_cipher_context *events_cipher_ctx; + struct pair_verify_context *pair_verify_ctx; + struct pair_setup_context *pair_setup_ctx; + + uint8_t shared_secret[32]; + + int server_fd; + int events_fd; + + union sockaddr_all sa; + + struct airplay_service *timing_svc; + struct airplay_service *control_svc; + + struct airplay_session *next; +}; + +struct airplay_metadata +{ + struct evbuffer *metadata; + struct evbuffer *artwork; + int artwork_fmt; +}; + +struct airplay_service +{ + int fd; + unsigned short port; + struct event *ev; +}; + +/* NTP timestamp definitions */ +#define FRAC 4294967296. /* 2^32 as a double */ +#define NTP_EPOCH_DELTA 0x83aa7e80 /* 2208988800 - that's 1970 - 1900 in seconds */ + +// TODO move to rtp_common +struct ntp_stamp +{ + uint32_t sec; + uint32_t frac; +}; + + +/* --------------------------- SEQUENCE DEFINITIONS ------------------------- */ + +struct airplay_seq_definition +{ + enum airplay_seq_type seq_type; + + // Called when a sequence ends, successfully or not. Shoulds also, if + // required, take care of notifying player and free the session. + void (*on_success)(struct airplay_session *rs); + void (*on_error)(struct airplay_session *rs); +}; + +struct airplay_seq_request +{ + enum airplay_seq_type seq_type; + const char *name; // Name of request (for logging) + enum evrtsp_cmd_type rtsp_type; + int (*payload_make)(struct evrtsp_request *req, struct airplay_session *rs, void *arg); + enum airplay_seq_type (*response_handler)(struct evrtsp_request *req, struct airplay_session *rs); + const char *content_type; + const char *uri; + bool proceed_on_rtsp_not_ok; // If true return code != RTSP_OK will not abort the sequence +}; + +struct airplay_seq_ctx +{ + struct airplay_seq_request *cur_request; + void (*on_success)(struct airplay_session *rs); + void (*on_error)(struct airplay_session *rs); + struct airplay_session *session; + void *payload_make_arg; + const char *log_caller; +}; + + +/* ------------------------------ MISC GLOBALS ------------------------------ */ + +/* +static const uint8_t airplay_auth_setup_pubkey[] = + "\x59\x02\xed\xe9\x0d\x4e\xf2\xbd\x4c\xb6\x8a\x63\x30\x03\x82\x07" + "\xa9\x4d\xbd\x50\xd8\xaa\x46\x5b\x5d\x8c\x01\x2a\x0c\x7e\x1d\x4e"; +*/ + +struct features_type_map +{ + uint32_t bit; + char *name; +}; + +// List of features announced by AirPlay 2 speakers +// Credit @invano, see https://emanuelecozzi.net/docs/airplay2 +static const struct features_type_map features_map[] = + { + { 0, "SupportsAirPlayVideoV1" }, + { 1, "SupportsAirPlayPhoto" }, + { 5, "SupportsAirPlaySlideshow" }, + { 7, "SupportsAirPlayScreen" }, + { 9, "SupportsAirPlayAudio" }, + { 11, "AudioRedunant" }, + { 14, "Authentication_4" }, // FairPlay authentication + { 15, "MetadataFeatures_0" }, // Send artwork image to receiver + { 16, "MetadataFeatures_1" }, // Send track progress status to receiver + { 17, "MetadataFeatures_2" }, // Send NowPlaying info via DAAP + { 18, "AudioFormats_0" }, + { 19, "AudioFormats_1" }, + { 20, "AudioFormats_2" }, + { 21, "AudioFormats_3" }, + { 23, "Authentication_1" }, // RSA authentication (NA) + { 26, "Authentication_8" }, // 26 || 51, MFi authentication + { 27, "SupportsLegacyPairing" }, + { 30, "HasUnifiedAdvertiserInfo" }, + { 32, "IsCarPlay" }, + { 32, "SupportsVolume" }, // !32 + { 33, "SupportsAirPlayVideoPlayQueue" }, + { 34, "SupportsAirPlayFromCloud" }, // 34 && flags_6_SupportsAirPlayFromCloud + { 35, "SupportsTLS_PSK" }, + { 38, "SupportsUnifiedMediaControl" }, + { 40, "SupportsBufferedAudio" }, // srcvers >= 354.54.6 && 40 + { 41, "SupportsPTP" }, // srcvers >= 366 && 41 + { 42, "SupportsScreenMultiCodec" }, + { 43, "SupportsSystemPairing" }, + { 44, "IsAPValeriaScreenSender" }, + { 46, "SupportsHKPairingAndAccessControl" }, + { 48, "SupportsCoreUtilsPairingAndEncryption" }, // 38 || 46 || 43 || 48 + { 49, "SupportsAirPlayVideoV2" }, + { 50, "MetadataFeatures_3" }, // Send NowPlaying info via bplist + { 51, "SupportsUnifiedPairSetupAndMFi" }, + { 52, "SupportsSetPeersExtendedMessage" }, + { 54, "SupportsAPSync" }, + { 55, "SupportsWoL" }, // 55 || 56 + { 56, "SupportsWoL" }, // 55 || 56 + { 58, "SupportsHangdogRemoteControl" }, // ((isAppleTV || isAppleAudioAccessory) && 58) || (isThirdPartyTV && flags_10) + { 59, "SupportsAudioStreamConnectionSetup" }, // 59 && !disableStreamConnectionSetup + { 60, "SupportsAudioMediaDataControl" }, // 59 && 60 && !disableMediaDataControl + { 61, "SupportsRFC2198Redundancy" }, + }; + +/* Keep in sync with enum airplay_devtype */ +static const char *airplay_devtype[] = +{ + "AirPort Express 1 - 802.11g", + "AirPort Express 2 - 802.11n", + "AirPort Express 3 - 802.11n", + "AppleTV", + "AppleTV4", + "HomePod", + "Other", +}; + +/* Struct with default quality levels */ +static struct media_quality airplay_quality_default = +{ + RAOP_QUALITY_SAMPLE_RATE_DEFAULT, + RAOP_QUALITY_BITS_PER_SAMPLE_DEFAULT, + RAOP_QUALITY_CHANNELS_DEFAULT +}; + +/* From player.c */ +extern struct event_base *evbase_player; + +/* AirTunes v2 time synchronization */ +static struct airplay_service timing_4svc; +static struct airplay_service timing_6svc; + +/* AirTunes v2 playback synchronization / control */ +static struct airplay_service control_4svc; +static struct airplay_service control_6svc; + +/* Metadata */ +static struct output_metadata *airplay_cur_metadata; + +/* Keep-alive timer - hack for ATV's with tvOS 10 */ +static struct event *keep_alive_timer; +static struct timeval keep_alive_tv = { RAOP_KEEP_ALIVE_INTERVAL, 0 }; + +/* Sessions */ +static struct airplay_master_session *airplay_master_sessions; +static struct airplay_session *airplay_sessions; + +// Forwards +static int +airplay_device_start(struct output_device *rd, int callback_id); +static void +sequence_start(enum airplay_seq_type seq_type, struct airplay_session *rs, void *arg, const char *log_caller); +static void +sequence_continue(struct airplay_seq_ctx *seq_ctx); + + +/* ------------------------------- MISC HELPERS ----------------------------- */ + +/* ALAC bits writer - big endian + * p outgoing buffer pointer + * val bitfield value + * blen bitfield length, max 8 bits + * bpos bit position in the current byte (pointed by *p) + */ +static inline void +alac_write_bits(uint8_t **p, uint8_t val, int blen, int *bpos) +{ + int lb; + int rb; + int bd; + + /* Remaining bits in the current byte */ + lb = 7 - *bpos + 1; + + /* Number of bits overflowing */ + rb = lb - blen; + + if (rb >= 0) + { + bd = val << rb; + if (*bpos == 0) + **p = bd; + else + **p |= bd; + + /* No over- nor underflow, we're done with this byte */ + if (rb == 0) + { + *p += 1; + *bpos = 0; + } + else + *bpos += blen; + } + else + { + /* Fill current byte */ + bd = val >> -rb; + **p |= bd; + + /* Overflow goes to the next byte */ + *p += 1; + **p = val << (8 + rb); + *bpos = -rb; + } +} + +/* Raw data must be little endian */ +static void +alac_encode(uint8_t *dst, uint8_t *raw, int len) +{ + uint8_t *maxraw; + int bpos; + + bpos = 0; + maxraw = raw + len; + + alac_write_bits(&dst, 1, 3, &bpos); /* channel=1, stereo */ + alac_write_bits(&dst, 0, 4, &bpos); /* unknown */ + alac_write_bits(&dst, 0, 8, &bpos); /* unknown */ + alac_write_bits(&dst, 0, 4, &bpos); /* unknown */ + alac_write_bits(&dst, 0, 1, &bpos); /* hassize */ + + alac_write_bits(&dst, 0, 2, &bpos); /* unused */ + alac_write_bits(&dst, 1, 1, &bpos); /* is-not-compressed */ + + for (; raw < maxraw; raw += 4) + { + /* Byteswap to big endian */ + alac_write_bits(&dst, *(raw + 1), 8, &bpos); + alac_write_bits(&dst, *raw, 8, &bpos); + alac_write_bits(&dst, *(raw + 3), 8, &bpos); + alac_write_bits(&dst, *(raw + 2), 8, &bpos); + } +} + +/* AirTunes v2 time synchronization helpers */ +static inline void +timespec_to_ntp(struct timespec *ts, struct ntp_stamp *ns) +{ + /* Seconds since NTP Epoch (1900-01-01) */ + ns->sec = ts->tv_sec + NTP_EPOCH_DELTA; + + ns->frac = (uint32_t)((double)ts->tv_nsec * 1e-9 * FRAC); +} + +static inline void +ntp_to_timespec(struct ntp_stamp *ns, struct timespec *ts) +{ + /* Seconds since Unix Epoch (1970-01-01) */ + ts->tv_sec = ns->sec - NTP_EPOCH_DELTA; + + ts->tv_nsec = (long)((double)ns->frac / (1e-9 * FRAC)); +} + +static inline int +airplay_timing_get_clock_ntp(struct ntp_stamp *ns) +{ + struct timespec ts; + int ret; + + ret = clock_gettime(CLOCK_MONOTONIC, &ts); + if (ret < 0) + { + DPRINTF(E_LOG, L_RAOP, "Couldn't get clock: %s\n", strerror(errno)); + + return -1; + } + + timespec_to_ntp(&ts, ns); + + return 0; +} + + +/* ----------------------- RAOP crypto stuff - from VLC --------------------- */ + +static int +encrypt_chacha(uint8_t *cipher, uint8_t *plain, size_t plain_len, const uint8_t *key, size_t key_len, const void *ad, size_t ad_len, uint8_t *tag, size_t tag_len, uint8_t *nonce, size_t nonce_len) +{ + gcry_cipher_hd_t hd; + + if (gcry_cipher_open(&hd, GCRY_CIPHER_CHACHA20, GCRY_CIPHER_MODE_POLY1305, 0) != GPG_ERR_NO_ERROR) + return -1; + + if (gcry_cipher_setkey(hd, key, key_len) != GPG_ERR_NO_ERROR) + goto error; + + if (gcry_cipher_setiv(hd, nonce, nonce_len) != GPG_ERR_NO_ERROR) + goto error; + + if (gcry_cipher_authenticate(hd, ad, ad_len) != GPG_ERR_NO_ERROR) + goto error; + + if (gcry_cipher_encrypt(hd, cipher, plain_len, plain, plain_len) != GPG_ERR_NO_ERROR) + goto error; + + if (gcry_cipher_gettag(hd, tag, tag_len) != GPG_ERR_NO_ERROR) + goto error; + + gcry_cipher_close(hd); + return 0; + + error: + gcry_cipher_close(hd); + return -1; +} + + +/* ------------------ Helpers for sending RAOP/RTSP requests ---------------- */ + +static int +request_header_auth_add(struct evrtsp_request *req, struct airplay_session *rs, const char *method, const char *uri) +{ + char ha1[33]; + char ha2[33]; + char ebuf[64]; + char auth[256]; + const char *hash_fmt; + const char *username; + uint8_t *hash_bytes; + size_t hashlen; + gcry_md_hd_t hd; + gpg_error_t gc_err; + int i; + int ret; + + rs->req_has_auth = 0; + + if (!rs->nonce) + return 0; + + if (!rs->password) + { + DPRINTF(E_LOG, L_RAOP, "Authentication required but no password found for device '%s'\n", rs->devname); + + return -2; + } + + if (rs->auth_quirk_itunes) + { + hash_fmt = "%02X"; /* Uppercase hex */ + username = "iTunes"; + } + else + { + hash_fmt = "%02x"; + username = ""; /* No username */ + } + + gc_err = gcry_md_open(&hd, GCRY_MD_MD5, 0); + if (gc_err != GPG_ERR_NO_ERROR) + { + gpg_strerror_r(gc_err, ebuf, sizeof(ebuf)); + DPRINTF(E_LOG, L_RAOP, "Could not open MD5: %s\n", ebuf); + + return -1; + } + + memset(ha1, 0, sizeof(ha1)); + memset(ha2, 0, sizeof(ha2)); + hashlen = gcry_md_get_algo_dlen(GCRY_MD_MD5); + + /* HA 1 */ + + gcry_md_write(hd, username, strlen(username)); + gcry_md_write(hd, ":", 1); + gcry_md_write(hd, rs->realm, strlen(rs->realm)); + gcry_md_write(hd, ":", 1); + gcry_md_write(hd, rs->password, strlen(rs->password)); + + hash_bytes = gcry_md_read(hd, GCRY_MD_MD5); + if (!hash_bytes) + { + DPRINTF(E_LOG, L_RAOP, "Could not read MD5 hash\n"); + + return -1; + } + + for (i = 0; i < hashlen; i++) + sprintf(ha1 + (2 * i), hash_fmt, hash_bytes[i]); + + /* RESET */ + gcry_md_reset(hd); + + /* HA 2 */ + gcry_md_write(hd, method, strlen(method)); + gcry_md_write(hd, ":", 1); + gcry_md_write(hd, uri, strlen(uri)); + + hash_bytes = gcry_md_read(hd, GCRY_MD_MD5); + if (!hash_bytes) + { + DPRINTF(E_LOG, L_RAOP, "Could not read MD5 hash\n"); + + return -1; + } + + for (i = 0; i < hashlen; i++) + sprintf(ha2 + (2 * i), hash_fmt, hash_bytes[i]); + + /* RESET */ + gcry_md_reset(hd); + + /* Final value */ + gcry_md_write(hd, ha1, 32); + gcry_md_write(hd, ":", 1); + gcry_md_write(hd, rs->nonce, strlen(rs->nonce)); + gcry_md_write(hd, ":", 1); + gcry_md_write(hd, ha2, 32); + + hash_bytes = gcry_md_read(hd, GCRY_MD_MD5); + if (!hash_bytes) + { + DPRINTF(E_LOG, L_RAOP, "Could not read MD5 hash\n"); + + return -1; + } + + for (i = 0; i < hashlen; i++) + sprintf(ha1 + (2 * i), hash_fmt, hash_bytes[i]); + + gcry_md_close(hd); + + /* Build header */ + ret = snprintf(auth, sizeof(auth), "Digest username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"%s\", response=\"%s\"", + username, rs->realm, rs->nonce, uri, ha1); + if ((ret < 0) || (ret >= sizeof(auth))) + { + DPRINTF(E_LOG, L_RAOP, "Authorization value header exceeds buffer size\n"); + + return -1; + } + + evrtsp_add_header(req->output_headers, "Authorization", auth); + + DPRINTF(E_DBG, L_RAOP, "Authorization header: %s\n", auth); + + rs->req_has_auth = 1; + + return 0; +} + +static int +response_header_auth_parse(struct airplay_session *rs, struct evrtsp_request *req) +{ + const char *param; + char *auth; + char *token; + char *ptr; + + if (rs->realm) + { + free(rs->realm); + rs->realm = NULL; + } + + if (rs->nonce) + { + free(rs->nonce); + rs->nonce = NULL; + } + + param = evrtsp_find_header(req->input_headers, "WWW-Authenticate"); + if (!param) + { + DPRINTF(E_LOG, L_RAOP, "WWW-Authenticate header not found\n"); + + return -1; + } + + DPRINTF(E_DBG, L_RAOP, "WWW-Authenticate: %s\n", param); + + if (strncmp(param, "Digest ", strlen("Digest ")) != 0) + { + DPRINTF(E_LOG, L_RAOP, "Unsupported authentication method: %s\n", param); + + return -1; + } + + auth = strdup(param); + if (!auth) + { + DPRINTF(E_LOG, L_RAOP, "Out of memory for WWW-Authenticate header copy\n"); + + return -1; + } + + token = strchr(auth, ' '); + token++; + + token = strtok_r(token, " =", &ptr); + while (token) + { + if (strcmp(token, "realm") == 0) + { + token = strtok_r(NULL, "=\"", &ptr); + if (!token) + break; + + rs->realm = strdup(token); + } + else if (strcmp(token, "nonce") == 0) + { + token = strtok_r(NULL, "=\"", &ptr); + if (!token) + break; + + rs->nonce = strdup(token); + } + + token = strtok_r(NULL, " =", &ptr); + } + + free(auth); + + if (!rs->realm || !rs->nonce) + { + DPRINTF(E_LOG, L_RAOP, "Could not find realm/nonce in WWW-Authenticate header\n"); + + if (rs->realm) + { + free(rs->realm); + rs->realm = NULL; + } + + if (rs->nonce) + { + free(rs->nonce); + rs->nonce = NULL; + } + + return -1; + } + + DPRINTF(E_DBG, L_RAOP, "Found realm: [%s], nonce: [%s]\n", rs->realm, rs->nonce); + + return 0; +} + +static int +request_headers_add(struct evrtsp_request *req, struct airplay_session *rs, enum evrtsp_cmd_type req_method) +{ + char buf[64]; + const char *method; + const char *url; + const char *user_agent; + int ret; + + method = evrtsp_method(req_method); + + snprintf(buf, sizeof(buf), "%d", rs->cseq); + evrtsp_add_header(req->output_headers, "CSeq", buf); + + rs->cseq++; + + user_agent = cfg_getstr(cfg_getsec(cfg, "general"), "user_agent"); + evrtsp_add_header(req->output_headers, "User-Agent", user_agent); + + /* Add Authorization header */ + url = (req_method == EVRTSP_REQ_OPTIONS) ? "*" : rs->session_url; + + ret = request_header_auth_add(req, rs, method, url); + if (ret < 0) + { + DPRINTF(E_LOG, L_RAOP, "Could not add Authorization header\n"); + + if (ret == -2) + rs->state = AIRPLAY_STATE_PASSWORD; + + return -1; + } + + snprintf(buf, sizeof(buf), "%" PRIX64, libhash); + evrtsp_add_header(req->output_headers, "Client-Instance", buf); + evrtsp_add_header(req->output_headers, "DACP-ID", buf); + + // We set Active-Remote as 32 bit unsigned decimal, as at least my device + // can't handle any larger. Must be aligned with volume_byactiveremote(). + snprintf(buf, sizeof(buf), "%" PRIu32, (uint32_t)rs->device_id); + evrtsp_add_header(req->output_headers, "Active-Remote", buf); + + if (rs->session) + evrtsp_add_header(req->output_headers, "Session", rs->session); + +#if AIRPLAY_USE_STREAMID + evrtsp_add_header(req->output_headers, "X-Apple-StreamID", "1"); +#endif + + /* Content-Length added automatically by evrtsp */ + + return 0; +} + +static int +session_url_set(struct airplay_session *rs) +{ + char *address = NULL; + char *intf; + unsigned short port; + int family; + int ret; + + // Determine local address, needed for SDP and session URL + evrtsp_connection_get_local_address(rs->ctrl, &address, &port, &family); + if (!address || (port == 0)) + { + DPRINTF(E_LOG, L_RAOP, "Could not determine local address\n"); + goto error; + } + + intf = strchr(address, '%'); + if (intf) + { + *intf = '\0'; + intf++; + } + + DPRINTF(E_DBG, L_RAOP, "Local address: %s (LL: %s) port %d\n", address, (intf) ? intf : "no", port); + + // Session ID and session URL + gcry_randomize(&rs->session_id, sizeof(rs->session_id), GCRY_STRONG_RANDOM); + + if (family == AF_INET) + ret = snprintf(rs->session_url, sizeof(rs->session_url), "rtsp://%s/%u", address, rs->session_id); + else + ret = snprintf(rs->session_url, sizeof(rs->session_url), "rtsp://[%s]/%u", address, rs->session_id); + if ((ret < 0) || (ret >= sizeof(rs->session_url))) + { + DPRINTF(E_LOG, L_RAOP, "Session URL length exceeds 127 characters\n"); + goto error; + } + + rs->local_address = address; + return 0; + + error: + free(address); + return -1; +} + +static void +metadata_rtptimes_get(uint32_t *start, uint32_t *display, uint32_t *pos, uint32_t *end, struct airplay_master_session *rms, struct output_metadata *metadata) +{ + struct rtp_session *rtp_session = rms->rtp_session; + // All the calculations with long ints to avoid surprises + int64_t sample_rate; + int64_t diff_ms; + int64_t elapsed_ms; + int64_t elapsed_samples; + int64_t len_samples; + + sample_rate = rtp_session->quality.sample_rate; + + // First calculate the rtptime that streaming of this item started: + // - at time metadata->pts the elapsed time was metadata->pos_ms + // - the time is now rms->cur_stamp.ts and the position is rms->cur_stamp.pos + // -> time since item started is elapsed_ms = metadata->pos_ms + (rms->cur_stamp.ts - metadata->pts) + // -> start must then be start = rms->cur_stamp.pos - elapsed_ms * sample_rate; + diff_ms = (rms->cur_stamp.ts.tv_sec - metadata->pts.tv_sec) * 1000L + (rms->cur_stamp.ts.tv_nsec - metadata->pts.tv_nsec) / 1000000L; + elapsed_ms = (int64_t)metadata->pos_ms + diff_ms; + elapsed_samples = elapsed_ms * sample_rate / 1000; + *start = rms->cur_stamp.pos - elapsed_samples; + +/* DPRINTF(E_DBG, L_RAOP, "pos_ms=%u, len_ms=%u, startup=%d, metadata.pts=%ld.%09ld, player.ts=%ld.%09ld, diff_ms=%" PRIi64 ", elapsed_ms=%" PRIi64 "\n", + metadata->pos_ms, metadata->len_ms, metadata->startup, metadata->pts.tv_sec, metadata->pts.tv_nsec, rms->cur_stamp.ts.tv_sec, rms->cur_stamp.ts.tv_nsec, diff_ms, elapsed_ms); +*/ + // Here's the deal with progress values: + // - display is always start minus a delay + // -> delay x1 if streaming is starting for this device (joining or not) + // -> delay x2 if stream is switching to a new song + // TODO what if we are just sending a keep_alive? + // - pos is the RTP time of the first sample for this song for this device + // -> start of song + // -> start of song + offset if device is joining in the middle of a song, + // or getting out of a pause or seeking + // - end is the RTP time of the last sample for this song + len_samples = (int64_t)metadata->len_ms * sample_rate / 1000; + *display = metadata->startup ? *start - RAOP_MD_DELAY_STARTUP : *start - RAOP_MD_DELAY_SWITCH; + *pos = MAX(rms->cur_stamp.pos, *start); + *end = len_samples ? *start + len_samples : *pos; + + DPRINTF(E_SPAM, L_RAOP, "start=%u, display=%u, pos=%u, end=%u, rtp_session.pos=%u, cur_stamp.pos=%u\n", + *start, *display, *pos, *end, rtp_session->pos, rms->cur_stamp.pos); +} + +// TODO not clear if Airplay 2 uses this header +static int +rtpinfo_header_add(struct evrtsp_request *req, struct airplay_session *rs, struct output_metadata *metadata) +{ + uint32_t start; + uint32_t display; + uint32_t pos; + uint32_t end; + char rtpinfo[32]; + int ret; + + metadata_rtptimes_get(&start, &display, &pos, &end, rs->master_session, metadata); + + ret = snprintf(rtpinfo, sizeof(rtpinfo), "rtptime=%u", start); + if ((ret < 0) || (ret >= sizeof(rtpinfo))) + { + DPRINTF(E_LOG, L_RAOP, "RTP-Info too big for buffer while sending metadata\n"); + return -1; + } + + evrtsp_add_header(req->output_headers, "RTP-Info", rtpinfo); + return 0; +} + +static void +rtsp_cipher(struct evbuffer *evbuf, void *arg, int encrypt) +{ + struct airplay_session *rs = arg; + uint8_t *out = NULL; + size_t out_len = 0; + int ret; + + uint8_t *in = evbuffer_pullup(evbuf, -1); + size_t in_len = evbuffer_get_length(evbuf); + + if (encrypt) + { + if (in_len < 4096) + DHEXDUMP(E_DBG, L_RAOP, in, in_len, "Encrypting outgoing request\n"); + else + DPRINTF(E_DBG, L_RAOP, "Encrypting outgoing request (size %zu)\n", in_len); + ret = pair_encrypt(&out, &out_len, in, in_len, rs->control_cipher_ctx); + } + else + { + ret = pair_decrypt(&out, &out_len, in, in_len, rs->control_cipher_ctx); + if (out_len < 4096) + DHEXDUMP(E_DBG, L_RAOP, out, out_len, "Decrypted incoming response\n"); + else + DPRINTF(E_DBG, L_RAOP, "Decrypted incoming response (size %zu)\n", out_len); + } + + evbuffer_drain(evbuf, in_len); + + if (ret < 0) + { +// TODO test this error condition seems that it can lead to a freeze + DPRINTF(E_LOG, L_RAOP, "Error while ciphering: %s\n", pair_cipher_errmsg(rs->control_cipher_ctx)); + return; + } + + evbuffer_add(evbuf, out, out_len); +} + + +/* ------------------------------ Session handling -------------------------- */ + +// Maps our internal state to the generic output state and then makes a callback +// to the player to tell that state +static void +session_status(struct airplay_session *rs) +{ + enum output_device_state state; + + switch (rs->state) + { + case AIRPLAY_STATE_PASSWORD: + state = OUTPUT_STATE_PASSWORD; + break; + case AIRPLAY_STATE_FAILED: + state = OUTPUT_STATE_FAILED; + break; + case AIRPLAY_STATE_STOPPED: + state = OUTPUT_STATE_STOPPED; + break; + case AIRPLAY_STATE_STARTUP ... AIRPLAY_STATE_RECORD: + state = OUTPUT_STATE_STARTUP; + break; + case AIRPLAY_STATE_CONNECTED: + state = OUTPUT_STATE_CONNECTED; + break; + case AIRPLAY_STATE_STREAMING: + state = OUTPUT_STATE_STREAMING; + break; + case AIRPLAY_STATE_TEARDOWN: + DPRINTF(E_LOG, L_RAOP, "Bug! session_status() called with transitional state (TEARDOWN)\n"); + state = OUTPUT_STATE_STOPPED; + break; + default: + DPRINTF(E_LOG, L_RAOP, "Bug! Unhandled state in session_status(): %d\n", rs->state); + state = OUTPUT_STATE_FAILED; + } + + outputs_cb(rs->callback_id, rs->device_id, state); + rs->callback_id = -1; +} + +static struct airplay_master_session * +master_session_make(struct media_quality *quality, bool encrypt) +{ + struct airplay_master_session *rms; + int ret; + + // First check if we already have a suitable session + for (rms = airplay_master_sessions; rms; rms = rms->next) + { + if (encrypt == rms->encrypt && quality_is_equal(quality, &rms->rtp_session->quality)) + return rms; + } + + // Let's create a master session + ret = outputs_quality_subscribe(quality); + if (ret < 0) + { + DPRINTF(E_LOG, L_RAOP, "Could not subscribe to required audio quality (%d/%d/%d)\n", quality->sample_rate, quality->bits_per_sample, quality->channels); + return NULL; + } + + CHECK_NULL(L_RAOP, rms = calloc(1, sizeof(struct airplay_master_session))); + + rms->rtp_session = rtp_session_new(quality, RAOP_PACKET_BUFFER_SIZE, 0); + if (!rms->rtp_session) + { + outputs_quality_unsubscribe(quality); + free(rms); + return NULL; + } + + rms->encrypt = encrypt; + rms->samples_per_packet = RAOP_SAMPLES_PER_PACKET; + rms->rawbuf_size = STOB(rms->samples_per_packet, quality->bits_per_sample, quality->channels); + rms->output_buffer_samples = OUTPUTS_BUFFER_DURATION * quality->sample_rate; + + CHECK_NULL(L_RAOP, rms->rawbuf = malloc(rms->rawbuf_size)); + CHECK_NULL(L_RAOP, rms->evbuf = evbuffer_new()); + + rms->next = airplay_master_sessions; + airplay_master_sessions = rms; + + return rms; +} + +static void +master_session_free(struct airplay_master_session *rms) +{ + if (!rms) + return; + + outputs_quality_unsubscribe(&rms->rtp_session->quality); + rtp_session_free(rms->rtp_session); + evbuffer_free(rms->evbuf); + free(rms->rawbuf); + free(rms); +} + +static void +master_session_cleanup(struct airplay_master_session *rms) +{ + struct airplay_master_session *s; + struct airplay_session *rs; + + // First check if any other session is using the master session + for (rs = airplay_sessions; rs; rs=rs->next) + { + if (rs->master_session == rms) + return; + } + + if (rms == airplay_master_sessions) + airplay_master_sessions = airplay_master_sessions->next; + else + { + for (s = airplay_master_sessions; s && (s->next != rms); s = s->next) + ; /* EMPTY */ + + if (!s) + DPRINTF(E_WARN, L_RAOP, "WARNING: struct airplay_master_session not found in list; BUG!\n"); + else + s->next = rms->next; + } + + master_session_free(rms); +} + +static void +session_free(struct airplay_session *rs) +{ + if (!rs) + return; + + if (rs->master_session) + master_session_cleanup(rs->master_session); + + if (rs->ctrl) + { + evrtsp_connection_set_closecb(rs->ctrl, NULL, NULL); + evrtsp_connection_free(rs->ctrl); + } + + if (rs->deferredev) + event_free(rs->deferredev); + + if (rs->server_fd >= 0) + close(rs->server_fd); + + pair_setup_free(rs->pair_setup_ctx); + pair_verify_free(rs->pair_verify_ctx); + pair_cipher_free(rs->control_cipher_ctx); + pair_cipher_free(rs->events_cipher_ctx); + + free(rs->local_address); + free(rs->realm); + free(rs->nonce); + free(rs->session); + free(rs->address); + free(rs->devname); + + free(rs); +} + +static void +session_cleanup(struct airplay_session *rs) +{ + struct airplay_session *s; + + if (rs == airplay_sessions) + airplay_sessions = airplay_sessions->next; + else + { + for (s = airplay_sessions; s && (s->next != rs); s = s->next) + ; /* EMPTY */ + + if (!s) + DPRINTF(E_WARN, L_RAOP, "WARNING: struct airplay_session not found in list; BUG!\n"); + else + s->next = rs->next; + } + + outputs_device_session_remove(rs->device_id); + + session_free(rs); +} + +static void +session_failure(struct airplay_session *rs) +{ + /* Session failed, let our user know */ + if (rs->state != AIRPLAY_STATE_PASSWORD) + rs->state = AIRPLAY_STATE_FAILED; + + session_status(rs); + + session_cleanup(rs); +} + +static void +deferred_session_failure_cb(int fd, short what, void *arg) +{ + struct airplay_session *rs = arg; + + DPRINTF(E_DBG, L_RAOP, "Cleaning up failed session (deferred) on device '%s'\n", rs->devname); + session_failure(rs); +} + +static void +deferred_session_failure(struct airplay_session *rs) +{ + struct timeval tv; + + if (rs->state != AIRPLAY_STATE_PASSWORD) + rs->state = AIRPLAY_STATE_FAILED; + + evutil_timerclear(&tv); + evtimer_add(rs->deferredev, &tv); +} + +static void +rtsp_close_cb(struct evrtsp_connection *evcon, void *arg) +{ + struct airplay_session *rs = arg; + + DPRINTF(E_LOG, L_RAOP, "Device '%s' closed RTSP connection\n", rs->devname); + + deferred_session_failure(rs); +} + +static void +session_success(struct airplay_session *rs) +{ + session_status(rs); + + session_cleanup(rs); +} + +static void +session_connected(struct airplay_session *rs) +{ + rs->state = AIRPLAY_STATE_CONNECTED; + + session_status(rs); +} + +static void +session_pair_success(struct airplay_session *rs) +{ + if (rs->next_seq != AIRPLAY_SEQ_CONTINUE) + { + sequence_start(rs->next_seq, rs, NULL, "pair_success"); + rs->next_seq = AIRPLAY_SEQ_CONTINUE; + return; + } + + session_success(rs); +} + +static int +session_connection_setup(struct airplay_session *rs, struct output_device *rd, int family) +{ + char *address; + char *intf; + unsigned short port; + int ret; + + rs->sa.ss.ss_family = family; + switch (family) + { + case AF_INET: + /* We always have the v4 services, so no need to check */ + if (!rd->v4_address) + return -1; + + address = rd->v4_address; + port = rd->v4_port; + + rs->timing_svc = &timing_4svc; + rs->control_svc = &control_4svc; + + ret = inet_pton(AF_INET, address, &rs->sa.sin.sin_addr); + break; + + case AF_INET6: + if (!rd->v6_address || rd->v6_disabled || (timing_6svc.fd < 0) || (control_6svc.fd < 0)) + return -1; + + address = rd->v6_address; + port = rd->v6_port; + + rs->timing_svc = &timing_6svc; + rs->control_svc = &control_6svc; + + intf = strchr(address, '%'); + if (intf) + *intf = '\0'; + + ret = inet_pton(AF_INET6, address, &rs->sa.sin6.sin6_addr); + + if (intf) + { + *intf = '%'; + + intf++; + + rs->sa.sin6.sin6_scope_id = if_nametoindex(intf); + if (rs->sa.sin6.sin6_scope_id == 0) + { + DPRINTF(E_LOG, L_RAOP, "Could not find interface %s\n", intf); + + ret = -1; + break; + } + } + + break; + + default: + return -1; + } + + if (ret <= 0) + { + DPRINTF(E_LOG, L_RAOP, "Device '%s' has invalid address (%s) for %s\n", rd->name, address, (family == AF_INET) ? "ipv4" : "ipv6"); + return -1; + } + + rs->ctrl = evrtsp_connection_new(address, port); + if (!rs->ctrl) + { + DPRINTF(E_LOG, L_RAOP, "Could not create control connection to '%s' (%s)\n", rd->name, address); + return -1; + } + + evrtsp_connection_set_base(rs->ctrl, evbase_player); + + rs->address = strdup(address); + rs->family = family; + + return 0; +} + +static struct airplay_session * +session_make(struct output_device *rd, int callback_id) +{ + struct airplay_session *rs; + struct airplay_extra *re; + int ret; + + re = rd->extra_device_info; + + + CHECK_NULL(L_RAOP, rs = calloc(1, sizeof(struct airplay_session))); + CHECK_NULL(L_RAOP, rs->deferredev = evtimer_new(evbase_player, deferred_session_failure_cb, rs)); + + rs->devname = strdup(rd->name); + rs->volume = rd->volume; + + rs->state = AIRPLAY_STATE_STOPPED; + rs->reqs_in_flight = 0; + rs->cseq = 1; + + rs->device_id = rd->id; + rs->callback_id = callback_id; + + rs->server_fd = -1; + + rs->password = rd->password; + + rs->supports_auth_setup = re->supports_auth_setup; + rs->wanted_metadata = re->wanted_metadata; + + rs->next_seq = AIRPLAY_SEQ_CONTINUE; + rs->pair_type = PAIR_HOMEKIT_NORMAL; +#if AIRPLAY_USE_PAIRING_TRANSIENT + // requires_auth will be set if the device returned a 470 RTSP_CONNECTION_AUTH_REQUIRED + if (!rd->requires_auth && re->supports_pairing_transient) + rs->pair_type = PAIR_HOMEKIT_TRANSIENT; +#endif + + switch (re->devtype) + { + case RAOP_DEV_APEX1_80211G: + rs->encrypt = 1; + rs->auth_quirk_itunes = 1; + break; + + case RAOP_DEV_APEX2_80211N: + rs->encrypt = 1; + rs->auth_quirk_itunes = 0; + break; + + case RAOP_DEV_APEX3_80211N: + rs->encrypt = 0; + rs->auth_quirk_itunes = 0; + break; + + case RAOP_DEV_APPLETV: + rs->encrypt = 0; + rs->auth_quirk_itunes = 0; + break; + + case RAOP_DEV_APPLETV4: + rs->encrypt = 0; + rs->auth_quirk_itunes = 0; + break; + + default: + rs->encrypt = re->encrypt; + rs->auth_quirk_itunes = 0; + } + + ret = session_connection_setup(rs, rd, AF_INET6); + if (ret < 0) + { + ret = session_connection_setup(rs, rd, AF_INET); + if (ret < 0) + goto error; + } + + rs->master_session = master_session_make(&rd->quality, rs->encrypt); + if (!rs->master_session) + { + DPRINTF(E_LOG, L_RAOP, "Could not attach a master session for device '%s'\n", rd->name); + goto error; + } + + // Attach to list of sessions + rs->next = airplay_sessions; + airplay_sessions = rs; + + // rs is now the official device session + outputs_device_session_add(rd->id, rs); + + return rs; + + error: + session_free(rs); + + return NULL; +} + + +/* ----------------------------- Metadata handling -------------------------- */ + +static void +airplay_metadata_free(struct airplay_metadata *rmd) +{ + if (!rmd) + return; + + if (rmd->metadata) + evbuffer_free(rmd->metadata); + if (rmd->artwork) + evbuffer_free(rmd->artwork); + + free(rmd); +} + +static void +airplay_metadata_purge(void) +{ + if (!airplay_cur_metadata) + return; + + airplay_metadata_free(airplay_cur_metadata->priv); + free(airplay_cur_metadata); + airplay_cur_metadata = NULL; +} + +// *** Thread: worker *** +static void * +airplay_metadata_prepare(struct output_metadata *metadata) +{ + struct db_queue_item *queue_item; + struct airplay_metadata *rmd; + struct evbuffer *tmp; + int ret; + + queue_item = db_queue_fetch_byitemid(metadata->item_id); + if (!queue_item) + { + DPRINTF(E_LOG, L_RAOP, "Could not fetch queue item\n"); + return NULL; + } + + CHECK_NULL(L_RAOP, rmd = calloc(1, sizeof(struct airplay_metadata))); + CHECK_NULL(L_RAOP, rmd->artwork = evbuffer_new()); + CHECK_NULL(L_RAOP, rmd->metadata = evbuffer_new()); + CHECK_NULL(L_RAOP, tmp = evbuffer_new()); + + ret = artwork_get_item(rmd->artwork, queue_item->file_id, ART_DEFAULT_WIDTH, ART_DEFAULT_HEIGHT, 0); + if (ret < 0) + { + DPRINTF(E_INFO, L_RAOP, "Failed to retrieve artwork for file '%s'; no artwork will be sent\n", queue_item->path); + evbuffer_free(rmd->artwork); + rmd->artwork = NULL; + } + + rmd->artwork_fmt = ret; + + ret = dmap_encode_queue_metadata(rmd->metadata, tmp, queue_item); + evbuffer_free(tmp); + free_queue_item(queue_item, 0); + if (ret < 0) + { + DPRINTF(E_LOG, L_RAOP, "Could not encode file metadata; metadata will not be sent\n"); + airplay_metadata_free(rmd); + return NULL; + } + + return rmd; +} + +static int +airplay_metadata_send_generic(struct airplay_session *rs, struct output_metadata *metadata, bool only_progress) +{ + struct airplay_metadata *rmd = metadata->priv; + + if (rs->wanted_metadata & RAOP_MD_WANTS_PROGRESS) + sequence_start(AIRPLAY_SEQ_SEND_PROGRESS, rs, metadata, "SET_PARAMETER (progress)"); + + if (!only_progress && (rs->wanted_metadata & RAOP_MD_WANTS_TEXT)) + sequence_start(AIRPLAY_SEQ_SEND_TEXT, rs, metadata, "SET_PARAMETER (text)"); + + if (!only_progress && (rs->wanted_metadata & RAOP_MD_WANTS_ARTWORK) && rmd->artwork) + sequence_start(AIRPLAY_SEQ_SEND_ARTWORK, rs, metadata, "SET_PARAMETER (artwork)"); + + return 0; +} + +static int +airplay_metadata_startup_send(struct airplay_session *rs) +{ + if (!rs->wanted_metadata || !airplay_cur_metadata) + return 0; + + airplay_cur_metadata->startup = true; + + return airplay_metadata_send_generic(rs, airplay_cur_metadata, false); +} + +static void +airplay_metadata_keep_alive_send(struct airplay_session *rs) +{ + sequence_start(AIRPLAY_SEQ_FEEDBACK, rs, NULL, "keep_alive"); +} + +static void +airplay_metadata_send(struct output_metadata *metadata) +{ + struct airplay_session *rs; + struct airplay_session *next; + int ret; + + for (rs = airplay_sessions; rs; rs = next) + { + next = rs->next; + + if (!(rs->state & AIRPLAY_STATE_F_CONNECTED) || !rs->wanted_metadata) + continue; + + ret = airplay_metadata_send_generic(rs, metadata, false); + if (ret < 0) + { + session_failure(rs); + continue; + } + } + + // Replace current metadata with the new stuff + airplay_metadata_purge(); + airplay_cur_metadata = metadata; +} + + +/* ------------------------------ Volume handling --------------------------- */ + +static float +airplay_volume_from_pct(int volume, char *name) +{ + float airplay_volume; + cfg_t *airplay; + int max_volume; + + max_volume = RAOP_CONFIG_MAX_VOLUME; + + airplay = cfg_gettsec(cfg, "airplay", name); + if (airplay) + max_volume = cfg_getint(airplay, "max_volume"); + + if ((max_volume < 1) || (max_volume > RAOP_CONFIG_MAX_VOLUME)) + { + DPRINTF(E_LOG, L_RAOP, "Config has bad max_volume (%d) for device '%s', using default instead\n", max_volume, name); + + max_volume = RAOP_CONFIG_MAX_VOLUME; + } + + /* RAOP volume + * -144.0 is off + * 0 - 100 maps to -30.0 - 0 + */ + if (volume > 0 && volume <= 100) + airplay_volume = -30.0 + ((float)max_volume * (float)volume * 30.0) / (100.0 * RAOP_CONFIG_MAX_VOLUME); + else + airplay_volume = -144.0; + + return airplay_volume; +} + +static int +airplay_volume_to_pct(struct output_device *rd, const char *volume) +{ + float airplay_volume; + cfg_t *airplay; + int max_volume; + + airplay_volume = atof(volume); + + // Basic sanity check + if (airplay_volume == 0.0 && volume[0] != '0') + { + DPRINTF(E_LOG, L_RAOP, "RAOP device volume is invalid: '%s'\n", volume); + return -1; + } + + max_volume = RAOP_CONFIG_MAX_VOLUME; + + airplay = cfg_gettsec(cfg, "airplay", rd->name); + if (airplay) + max_volume = cfg_getint(airplay, "max_volume"); + + if ((max_volume < 1) || (max_volume > RAOP_CONFIG_MAX_VOLUME)) + { + DPRINTF(E_LOG, L_RAOP, "Config has bad max_volume (%d) for device '%s', using default instead\n", max_volume, rd->name); + max_volume = RAOP_CONFIG_MAX_VOLUME; + } + + // RAOP volume: -144.0 is off, -30.0 - 0 scaled by max_volume maps to 0 - 100 + if (airplay_volume > -30.0 && airplay_volume <= 0.0) + return (int)(100.0 * (airplay_volume / 30.0 + 1.0) * RAOP_CONFIG_MAX_VOLUME / (float)max_volume); + else + return 0; +} + +/* Volume in [0 - 100] */ +static int +airplay_set_volume_one(struct output_device *device, int callback_id) +{ + struct airplay_session *rs = device->session; + + if (!rs || !(rs->state & AIRPLAY_STATE_F_CONNECTED)) + return 0; + + rs->volume = device->volume; + rs->callback_id = callback_id; + + sequence_start(AIRPLAY_SEQ_SEND_VOLUME, rs, NULL, "set_volume_one"); + + return 1; +} + +static void +airplay_keep_alive_timer_cb(int fd, short what, void *arg) +{ + struct airplay_session *rs; + + if (!airplay_sessions) + { + event_del(keep_alive_timer); + return; + } + + for (rs = airplay_sessions; rs; rs = rs->next) + { + if (!(rs->state & AIRPLAY_STATE_F_CONNECTED)) + continue; + + airplay_metadata_keep_alive_send(rs); + } + + evtimer_add(keep_alive_timer, &keep_alive_tv); +} + + +/* -------------------- Creation and sending of RTP packets ---------------- */ + +static int +packet_encrypt(uint8_t **out, size_t *out_len, struct rtp_packet *pkt, struct airplay_session *rs) +{ + uint8_t authtag[16]; + uint8_t nonce[12] = { 0 }; + int nonce_offset = 4; + uint8_t *write_ptr; + int ret; + + // Alloc so authtag and nonce can be appended + *out_len = pkt->data_len + sizeof(authtag) + sizeof(nonce) - nonce_offset; + *out = malloc(*out_len); + write_ptr = *out; + + // Using seqnum as nonce not very secure, but means that when we resend + // packets they will be identical to the original + memcpy(nonce + nonce_offset, &pkt->seqnum, sizeof(pkt->seqnum)); + + // The RTP header is not encrypted + memcpy(write_ptr, pkt->header, pkt->header_len); + write_ptr = *out + pkt->header_len; + + // Timestamp and SSRC are used as AAD = pkt->header + 4, len 8 + ret = encrypt_chacha(write_ptr, pkt->payload, pkt->payload_len, rs->shared_secret, sizeof(rs->shared_secret), pkt->header + 4, 8, authtag, sizeof(authtag), nonce, sizeof(nonce)); + if (ret < 0) + { + free(*out); + return -1; + } + + write_ptr += pkt->payload_len; + memcpy(write_ptr, authtag, sizeof(authtag)); + write_ptr += sizeof(authtag); + memcpy(write_ptr, nonce + nonce_offset, sizeof(nonce) - nonce_offset); + + return 0; +} + +static int +packet_send(struct airplay_session *rs, struct rtp_packet *pkt) +{ + uint8_t *encrypted; + size_t encrypted_len; + int ret; + + if (!rs) + return -1; + + ret = packet_encrypt(&encrypted, &encrypted_len, pkt, rs); + if (ret < 0) + return -1; + + ret = send(rs->server_fd, encrypted, encrypted_len, 0); + free(encrypted); + if (ret < 0) + { + DPRINTF(E_LOG, L_RAOP, "Send error for '%s': %s\n", rs->devname, strerror(errno)); + + // Can't free it right away, it would make the ->next in the calling + // master_session and session loops invalid + deferred_session_failure(rs); + return -1; + } + else if (ret != encrypted_len) + { + DPRINTF(E_WARN, L_RAOP, "Partial send (%d) for '%s'\n", ret, rs->devname); + return -1; + } + +/* DPRINTF(E_DBG, L_RAOP, "RTP PACKET seqnum %u, rtptime %u, payload 0x%x, pktbuf_s %zu\n", + rs->master_session->rtp_session->seqnum, + rs->master_session->rtp_session->pos, + pkt->header[1], + rs->master_session->rtp_session->pktbuf_len + ); +*/ + return 0; +} + +static void +control_packet_send(struct airplay_session *rs, struct rtp_packet *pkt) +{ + int len; + int ret; + + switch (rs->sa.ss.ss_family) + { + case AF_INET: + rs->sa.sin.sin_port = htons(rs->control_port); + len = sizeof(rs->sa.sin); + break; + + case AF_INET6: + rs->sa.sin6.sin6_port = htons(rs->control_port); + len = sizeof(rs->sa.sin6); + break; + + default: + DPRINTF(E_WARN, L_RAOP, "Unknown family %d\n", rs->sa.ss.ss_family); + return; + } + + ret = sendto(rs->control_svc->fd, pkt->data, pkt->data_len, 0, &rs->sa.sa, len); + if (ret < 0) + DPRINTF(E_LOG, L_RAOP, "Could not send playback sync to device '%s': %s\n", rs->devname, strerror(errno)); +} + +static void +packets_resend(struct airplay_session *rs, uint16_t seqnum, int len) +{ + struct rtp_session *rtp_session; + struct rtp_packet *pkt; + uint16_t s; + int i; + bool pkt_missing = false; + + rtp_session = rs->master_session->rtp_session; + + DPRINTF(E_DBG, L_RAOP, "Got retransmit request from '%s': seqnum %" PRIu16 " (len %d), last RTP session seqnum %" PRIu16 " (len %zu)\n", + rs->devname, seqnum, len, rtp_session->seqnum - 1, rtp_session->pktbuf_len); + + // Note that seqnum may wrap around, so we don't use it for counting + for (i = 0, s = seqnum; i < len; i++, s++) + { + pkt = rtp_packet_get(rtp_session, s); + if (pkt) + packet_send(rs, pkt); + else + pkt_missing = true; + } + + if (pkt_missing) + DPRINTF(E_WARN, L_RAOP, "Device '%s' retransmit request for seqnum %" PRIu16 " (len %d) is outside buffer range (last seqnum %" PRIu16 ", len %zu)\n", + rs->devname, seqnum, len, rtp_session->seqnum - 1, rtp_session->pktbuf_len); +} + +static int +packets_send(struct airplay_master_session *rms) +{ + struct rtp_packet *pkt; + struct airplay_session *rs; + + pkt = rtp_packet_next(rms->rtp_session, ALAC_HEADER_LEN + rms->rawbuf_size, rms->samples_per_packet, RAOP_RTP_PAYLOADTYPE, 0); + + alac_encode(pkt->payload, rms->rawbuf, rms->rawbuf_size); + + for (rs = airplay_sessions; rs; rs = rs->next) + { + if (rs->master_session != rms) + continue; + + // Device just joined + if (rs->state == AIRPLAY_STATE_CONNECTED) + { + pkt->header[1] = (1 << 7) | RAOP_RTP_PAYLOADTYPE; + packet_send(rs, pkt); + } + else if (rs->state == AIRPLAY_STATE_STREAMING) + { + pkt->header[1] = RAOP_RTP_PAYLOADTYPE; + packet_send(rs, pkt); + } + } + + // Commits packet to retransmit buffer, and prepares the session for the next packet + rtp_packet_commit(rms->rtp_session, pkt); + + return 0; +} + +// Overview of rtptimes as they should be when starting a stream, and assuming +// the first rtptime (pos) is 88200: +// sync pkt: cur_pos = 0, rtptime = 88200 +// audio pkt: rtptime = 88200 +// RECORD: rtptime = 88200 +// SET_PARAMETER text/artwork: +// rtptime = 88200 +// SET_PARAMETER progress: +// progress = 72840/~88200/[len] +static inline void +timestamp_set(struct airplay_master_session *rms, struct timespec ts) +{ + // The last write from the player had a timestamp which has been passed to + // this function as ts. This is the player clock, which is more precise than + // the actual clock because it gives us a calculated time reference, which is + // independent of how busy the thread is. We save that here, we need this for + // reference when sending sync packets and progress. + rms->cur_stamp.ts = ts; + + // So what rtptime should be playing, i.e. coming out of the speaker, at time + // ts (which is normally "now")? Let's calculate by example: + // - we started playback with a rtptime (pos) of X + // - up until time ts we have received a 1000 samples from the player + // - rms->output_buffer_samples is configured to 400 samples + // -> we should be playing rtptime X + 600 + // + // So how do we measure samples received from player? We know that from the + // pos, which says how much has been sent to the device, and from rms->evbuf, + // which is the unsent stuff being buffered: + // - received = (pos - X) + rms->evbuf_samples + // + // This means the rtptime is computed as: + // - rtptime = X + received - rms->output_buffer_samples + // -> rtptime = X + (pos - X) + rms->evbuf_samples - rms->out_buffer_samples + // -> rtptime = pos + rms->evbuf_samples - rms->output_buffer_samples + rms->cur_stamp.pos = rms->rtp_session->pos + rms->evbuf_samples - rms->output_buffer_samples; +} + +static void +packets_sync_send(struct airplay_master_session *rms) +{ + struct rtp_packet *sync_pkt; + struct airplay_session *rs; + struct timespec ts; + bool is_sync_time; + + // Check if it is time send a sync packet to sessions that are already running + is_sync_time = rtp_sync_is_time(rms->rtp_session); + + // Just used for logging, the clock shouldn't be too far from rms->cur_stamp.ts + clock_gettime(CLOCK_MONOTONIC, &ts); + + for (rs = airplay_sessions; rs; rs = rs->next) + { + if (rs->master_session != rms) + continue; + + // A device has joined and should get an init sync packet + if (rs->state == AIRPLAY_STATE_CONNECTED) + { + sync_pkt = rtp_sync_packet_next(rms->rtp_session, rms->cur_stamp, 0x90); + control_packet_send(rs, sync_pkt); + + DPRINTF(E_DBG, L_RAOP, "Start sync packet sent to '%s': cur_pos=%" PRIu32 ", cur_ts=%ld.%09ld, clock=%ld.%09ld, rtptime=%" PRIu32 "\n", + rs->devname, rms->cur_stamp.pos, rms->cur_stamp.ts.tv_sec, rms->cur_stamp.ts.tv_nsec, ts.tv_sec, ts.tv_nsec, rms->rtp_session->pos); + } + else if (is_sync_time && rs->state == AIRPLAY_STATE_STREAMING) + { + sync_pkt = rtp_sync_packet_next(rms->rtp_session, rms->cur_stamp, 0x80); + control_packet_send(rs, sync_pkt); + } + } +} + + +/* ------------------------------ Time service ------------------------------ */ + +static void +airplay_timing_cb(int fd, short what, void *arg) +{ + union sockaddr_all sa; + uint8_t req[32]; + uint8_t res[32]; + struct ntp_stamp recv_stamp; + struct ntp_stamp xmit_stamp; + struct airplay_service *svc; + int len; + int ret; + + svc = (struct airplay_service *)arg; + + ret = airplay_timing_get_clock_ntp(&recv_stamp); + if (ret < 0) + { + DPRINTF(E_LOG, L_RAOP, "Couldn't get receive timestamp\n"); + + goto readd; + } + + len = sizeof(sa.ss); + ret = recvfrom(svc->fd, req, sizeof(req), 0, &sa.sa, (socklen_t *)&len); + if (ret < 0) + { + DPRINTF(E_LOG, L_RAOP, "Error reading timing request: %s\n", strerror(errno)); + + goto readd; + } + + if (ret != 32) + { + DPRINTF(E_DBG, L_RAOP, "Got timing request with size %d\n", ret); + + goto readd; + } + + if ((req[0] != 0x80) || (req[1] != 0xd2)) + { + DPRINTF(E_LOG, L_RAOP, "Packet header doesn't match timing request (got 0x%02x%02x, expected 0x80d2)\n", req[0], req[1]); + + goto readd; + } + + memset(res, 0, sizeof(res)); + + /* Header */ + res[0] = 0x80; + res[1] = 0xd3; + res[2] = req[2]; + + /* Copy client timestamp */ + memcpy(res + 8, req + 24, 8); + + /* Receive timestamp */ + recv_stamp.sec = htobe32(recv_stamp.sec); + recv_stamp.frac = htobe32(recv_stamp.frac); + memcpy(res + 16, &recv_stamp.sec, 4); + memcpy(res + 20, &recv_stamp.frac, 4); + + /* Transmit timestamp */ + ret = airplay_timing_get_clock_ntp(&xmit_stamp); + if (ret < 0) + { + DPRINTF(E_LOG, L_RAOP, "Couldn't get transmit timestamp, falling back to receive timestamp\n"); + + /* Still better than failing altogether + * recv/xmit are close enough that it shouldn't matter much + */ + memcpy(res + 24, &recv_stamp.sec, 4); + memcpy(res + 28, &recv_stamp.frac, 4); + } + else + { + xmit_stamp.sec = htobe32(xmit_stamp.sec); + xmit_stamp.frac = htobe32(xmit_stamp.frac); + memcpy(res + 24, &xmit_stamp.sec, 4); + memcpy(res + 28, &xmit_stamp.frac, 4); + } + + ret = sendto(svc->fd, res, sizeof(res), 0, &sa.sa, len); + if (ret < 0) + { + DPRINTF(E_LOG, L_RAOP, "Could not send timing reply: %s\n", strerror(errno)); + + goto readd; + } + + readd: + ret = event_add(svc->ev, NULL); + if (ret < 0) + { + DPRINTF(E_LOG, L_RAOP, "Couldn't re-add event for timing requests\n"); + + return; + } +} + +static int +airplay_timing_start_one(struct airplay_service *svc, int family) +{ + union sockaddr_all sa; + int on; + int len; + int ret; + int timing_port; + +#ifdef SOCK_CLOEXEC + svc->fd = socket(family, SOCK_DGRAM | SOCK_CLOEXEC, 0); +#else + svc->fd = socket(family, SOCK_DGRAM, 0); +#endif + if (svc->fd < 0) + { + DPRINTF(E_LOG, L_RAOP, "Couldn't make timing socket: %s\n", strerror(errno)); + + return -1; + } + + if (family == AF_INET6) + { + on = 1; + ret = setsockopt(svc->fd, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on)); + if (ret < 0) + { + DPRINTF(E_LOG, L_RAOP, "Could not set IPV6_V6ONLY on timing socket: %s\n", strerror(errno)); + + goto out_fail; + } + } + + memset(&sa, 0, sizeof(union sockaddr_all)); + sa.ss.ss_family = family; + + timing_port = cfg_getint(cfg_getsec(cfg, "airplay_shared"), "timing_port"); + switch (family) + { + case AF_INET: + sa.sin.sin_addr.s_addr = INADDR_ANY; + sa.sin.sin_port = htons(timing_port); + len = sizeof(sa.sin); + break; + + case AF_INET6: + sa.sin6.sin6_addr = in6addr_any; + sa.sin6.sin6_port = htons(timing_port); + len = sizeof(sa.sin6); + break; + } + + ret = bind(svc->fd, &sa.sa, len); + if (ret < 0) + { + DPRINTF(E_LOG, L_RAOP, "Couldn't bind timing socket: %s\n", strerror(errno)); + + goto out_fail; + } + + len = sizeof(sa.ss); + ret = getsockname(svc->fd, &sa.sa, (socklen_t *)&len); + if (ret < 0) + { + DPRINTF(E_LOG, L_RAOP, "Couldn't get timing socket name: %s\n", strerror(errno)); + + goto out_fail; + } + + switch (family) + { + case AF_INET: + svc->port = ntohs(sa.sin.sin_port); + DPRINTF(E_DBG, L_RAOP, "Timing IPv4 port: %d\n", svc->port); + break; + + case AF_INET6: + svc->port = ntohs(sa.sin6.sin6_port); + DPRINTF(E_DBG, L_RAOP, "Timing IPv6 port: %d\n", svc->port); + break; + } + + svc->ev = event_new(evbase_player, svc->fd, EV_READ, airplay_timing_cb, svc); + if (!svc->ev) + { + DPRINTF(E_LOG, L_RAOP, "Out of memory for airplay_service event\n"); + + goto out_fail; + } + + event_add(svc->ev, NULL); + + return 0; + + out_fail: + close(svc->fd); + svc->fd = -1; + svc->port = 0; + + return -1; +} + +static void +airplay_timing_stop(void) +{ + if (timing_4svc.ev) + event_free(timing_4svc.ev); + + if (timing_6svc.ev) + event_free(timing_6svc.ev); + + close(timing_4svc.fd); + + timing_4svc.fd = -1; + timing_4svc.port = 0; + + close(timing_6svc.fd); + + timing_6svc.fd = -1; + timing_6svc.port = 0; +} + +static int +airplay_timing_start(int v6enabled) +{ + int ret; + + if (v6enabled) + { + ret = airplay_timing_start_one(&timing_6svc, AF_INET6); + if (ret < 0) + DPRINTF(E_WARN, L_RAOP, "Could not start timing service on IPv6\n"); + } + + ret = airplay_timing_start_one(&timing_4svc, AF_INET); + if (ret < 0) + { + DPRINTF(E_LOG, L_RAOP, "Could not start timing service on IPv4\n"); + + airplay_timing_stop(); + return -1; + } + + return 0; +} + + +/* ----------------- Control service (retransmission and sync) ---------------*/ + +static void +airplay_control_cb(int fd, short what, void *arg) +{ + char address[INET6_ADDRSTRLEN]; + union sockaddr_all sa; + uint8_t req[8]; + struct airplay_session *rs; + struct airplay_service *svc; + uint16_t seq_start; + uint16_t seq_len; + int len; + int ret; + + svc = (struct airplay_service *)arg; + + len = sizeof(sa.ss); + ret = recvfrom(svc->fd, req, sizeof(req), 0, &sa.sa, (socklen_t *)&len); + if (ret < 0) + { + DPRINTF(E_LOG, L_RAOP, "Error reading control request: %s\n", strerror(errno)); + + goto readd; + } + + if (ret != 8) + { + DPRINTF(E_DBG, L_RAOP, "Got control request with size %d\n", ret); + + goto readd; + } + + switch (sa.ss.ss_family) + { + case AF_INET: + if (svc != &control_4svc) + goto readd; + + for (rs = airplay_sessions; rs; rs = rs->next) + { + if ((rs->sa.ss.ss_family == AF_INET) + && (sa.sin.sin_addr.s_addr == rs->sa.sin.sin_addr.s_addr)) + break; + } + + if (!rs) + ret = (inet_ntop(AF_INET, &sa.sin.sin_addr.s_addr, address, sizeof(address)) != NULL); + + break; + + case AF_INET6: + if (svc != &control_6svc) + goto readd; + + for (rs = airplay_sessions; rs; rs = rs->next) + { + if ((rs->sa.ss.ss_family == AF_INET6) + && IN6_ARE_ADDR_EQUAL(&sa.sin6.sin6_addr, &rs->sa.sin6.sin6_addr)) + break; + } + + if (!rs) + ret = (inet_ntop(AF_INET6, &sa.sin6.sin6_addr.s6_addr, address, sizeof(address)) != NULL); + + break; + + default: + DPRINTF(E_LOG, L_RAOP, "Control svc: Unknown address family %d\n", sa.ss.ss_family); + goto readd; + } + + if (!rs) + { + if (!ret) + DPRINTF(E_LOG, L_RAOP, "Control request from [error: %s]; not a RAOP client\n", strerror(errno)); + else + DPRINTF(E_LOG, L_RAOP, "Control request from %s; not a RAOP client\n", address); + + goto readd; + } + + if ((req[0] != 0x80) || (req[1] != 0xd5)) + { + DPRINTF(E_LOG, L_RAOP, "Packet header doesn't match retransmit request (got 0x%02x%02x, expected 0x80d5)\n", req[0], req[1]); + + goto readd; + } + + memcpy(&seq_start, req + 4, 2); + memcpy(&seq_len, req + 6, 2); + + seq_start = be16toh(seq_start); + seq_len = be16toh(seq_len); + + packets_resend(rs, seq_start, seq_len); + + readd: + ret = event_add(svc->ev, NULL); + if (ret < 0) + { + DPRINTF(E_LOG, L_RAOP, "Couldn't re-add event for control requests\n"); + + return; + } +} + +static int +airplay_control_start_one(struct airplay_service *svc, int family) +{ + union sockaddr_all sa; + int on; + int len; + int ret; + int control_port; + +#ifdef SOCK_CLOEXEC + svc->fd = socket(family, SOCK_DGRAM | SOCK_CLOEXEC, 0); +#else + svc->fd = socket(family, SOCK_DGRAM, 0); +#endif + if (svc->fd < 0) + { + DPRINTF(E_LOG, L_RAOP, "Couldn't make control socket: %s\n", strerror(errno)); + + return -1; + } + + if (family == AF_INET6) + { + on = 1; + ret = setsockopt(svc->fd, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on)); + if (ret < 0) + { + DPRINTF(E_LOG, L_RAOP, "Could not set IPV6_V6ONLY on control socket: %s\n", strerror(errno)); + + goto out_fail; + } + } + + memset(&sa, 0, sizeof(union sockaddr_all)); + sa.ss.ss_family = family; + + control_port = cfg_getint(cfg_getsec(cfg, "airplay_shared"), "control_port"); + switch (family) + { + case AF_INET: + sa.sin.sin_addr.s_addr = INADDR_ANY; + sa.sin.sin_port = htons(control_port); + len = sizeof(sa.sin); + break; + + case AF_INET6: + sa.sin6.sin6_addr = in6addr_any; + sa.sin6.sin6_port = htons(control_port); + len = sizeof(sa.sin6); + break; + } + + ret = bind(svc->fd, &sa.sa, len); + if (ret < 0) + { + DPRINTF(E_LOG, L_RAOP, "Couldn't bind control socket: %s\n", strerror(errno)); + + goto out_fail; + } + + len = sizeof(sa.ss); + ret = getsockname(svc->fd, &sa.sa, (socklen_t *)&len); + if (ret < 0) + { + DPRINTF(E_LOG, L_RAOP, "Couldn't get control socket name: %s\n", strerror(errno)); + + goto out_fail; + } + + switch (family) + { + case AF_INET: + svc->port = ntohs(sa.sin.sin_port); + DPRINTF(E_DBG, L_RAOP, "Control IPv4 port: %d\n", svc->port); + break; + + case AF_INET6: + svc->port = ntohs(sa.sin6.sin6_port); + DPRINTF(E_DBG, L_RAOP, "Control IPv6 port: %d\n", svc->port); + break; + } + + svc->ev = event_new(evbase_player, svc->fd, EV_READ, airplay_control_cb, svc); + if (!svc->ev) + { + DPRINTF(E_LOG, L_RAOP, "Out of memory for control event\n"); + + goto out_fail; + } + + event_add(svc->ev, NULL); + + return 0; + + out_fail: + close(svc->fd); + svc->fd = -1; + svc->port = 0; + + return -1; +} + +static void +airplay_control_stop(void) +{ + if (control_4svc.ev) + event_free(control_4svc.ev); + + if (control_6svc.ev) + event_free(control_6svc.ev); + + close(control_4svc.fd); + + control_4svc.fd = -1; + control_4svc.port = 0; + + close(control_6svc.fd); + + control_6svc.fd = -1; + control_6svc.port = 0; +} + +static int +airplay_control_start(int v6enabled) +{ + int ret; + + if (v6enabled) + { + ret = airplay_control_start_one(&control_6svc, AF_INET6); + if (ret < 0) + DPRINTF(E_WARN, L_RAOP, "Could not start control service on IPv6\n"); + } + + ret = airplay_control_start_one(&control_4svc, AF_INET); + if (ret < 0) + { + DPRINTF(E_LOG, L_RAOP, "Could not start control service on IPv4\n"); + + airplay_control_stop(); + return -1; + } + + return 0; +} + + +/* ----------------------------- Event receiver ------------------------------*/ + +static void +event_channel_cb(int fd, short what, void *arg) +{ + struct airplay_session *rs = arg; + ssize_t in_len; + int ret; + uint8_t in[4096]; //TODO + uint8_t *out; + size_t out_len = 0; + + in_len = recv(fd, in, sizeof(in), 0); + if (in_len < 0) + DPRINTF(E_WARN, L_RAOP, "Possible disconnect from event channel from %s\n", rs->devname); + // TODO end session + + if (in_len <= 0) + return; + + DPRINTF(E_DBG, L_RAOP, "GOT AN EVENT, len was %zd\n", in_len); + + if (in_len == sizeof(in)) + return; // Longer than expected, give up + + ret = pair_decrypt(&out, &out_len, in, in_len, rs->events_cipher_ctx); + if (ret < 0) + { + DPRINTF(E_DBG, L_RAOP, "Decryption error was: %s\n", pair_cipher_errmsg(rs->events_cipher_ctx)); + return; + } + + DHEXDUMP(E_DBG, L_RAOP, out, out_len, "Decrypted incoming event\n"); +} + + +/* ----------------- Handlers for sending RAOP/RTSP requests ---------------- */ + +static int +payload_make_flush(struct evrtsp_request *req, struct airplay_session *rs, void *arg) +{ + struct airplay_master_session *rms = rs->master_session; + char buf[64]; + int ret; + + /* Restart sequence */ + ret = snprintf(buf, sizeof(buf), "seq=%" PRIu16 ";rtptime=%u", rms->rtp_session->seqnum, rms->rtp_session->pos); + if ((ret < 0) || (ret >= sizeof(buf))) + { + DPRINTF(E_LOG, L_RAOP, "RTP-Info too big for buffer in FLUSH request\n"); + return -1; + } + evrtsp_add_header(req->output_headers, "RTP-Info", buf); + + return 0; +} + +static int +payload_make_teardown(struct evrtsp_request *req, struct airplay_session *rs, void *arg) +{ + // Normally we update status when we get the response, but teardown is an + // exception because we want to stop writing to the device immediately + rs->state = AIRPLAY_STATE_TEARDOWN; + return 0; +} + +static int +payload_make_set_volume(struct evrtsp_request *req, struct airplay_session *rs, void *arg) +{ + float raop_volume; + int ret; + + raop_volume = airplay_volume_from_pct(rs->volume, rs->devname); + + /* Don't let locales get in the way here */ + /* We use -%d and -(int)raop_volume so -0.3 won't become 0.3 */ + ret = evbuffer_add_printf(req->output_buffer, "volume: -%d.%06d\r\n", -(int)raop_volume, -(int)(1000000.0 * (raop_volume - (int)raop_volume))); + if (ret < 0) + { + DPRINTF(E_LOG, L_RAOP, "Out of memory for SET_PARAMETER payload (volume)\n"); + return -1; + } + + return 0; +} + +static int +payload_make_send_progress(struct evrtsp_request *req, struct airplay_session *rs, void *arg) +{ + struct output_metadata *metadata = arg; + uint32_t start; + uint32_t display; + uint32_t pos; + uint32_t end; + int ret; + + metadata_rtptimes_get(&start, &display, &pos, &end, rs->master_session, metadata); + + ret = evbuffer_add_printf(req->output_buffer, "progress: %u/%u/%u\r\n", display, pos, end); + if (ret < 0) + { + DPRINTF(E_LOG, L_RAOP, "Could not build progress string for sending\n"); + return -1; + } + + ret = rtpinfo_header_add(req, rs, metadata); + if (ret < 0) + return -1; + + return 0; +} + +static int +payload_make_send_artwork(struct evrtsp_request *req, struct airplay_session *rs, void *arg) +{ + struct output_metadata *metadata = arg; + struct airplay_metadata *rmd = metadata->priv; + char *ctype; + uint8_t *buf; + size_t len; + int ret; + + switch (rmd->artwork_fmt) + { + case ART_FMT_PNG: + ctype = "image/png"; + break; + + case ART_FMT_JPEG: + ctype = "image/jpeg"; + break; + + default: + DPRINTF(E_LOG, L_RAOP, "Unsupported artwork format %d\n", rmd->artwork_fmt); + return -1; + } + + buf = evbuffer_pullup(rmd->artwork, -1); + len = evbuffer_get_length(rmd->artwork); + + ret = evbuffer_add(req->output_buffer, buf, len); + if (ret != 0) + { + DPRINTF(E_LOG, L_RAOP, "Could not copy artwork for sending\n"); + return -1; + } + + ret = rtpinfo_header_add(req, rs, metadata); + if (ret < 0) + return -1; + + evrtsp_add_header(req->output_headers, "Content-Type", ctype); + + return 0; +} + +static int +payload_make_send_text(struct evrtsp_request *req, struct airplay_session *rs, void *arg) +{ + struct output_metadata *metadata = arg; + struct airplay_metadata *rmd = metadata->priv; + uint8_t *buf; + size_t len; + int ret; + + buf = evbuffer_pullup(rmd->metadata, -1); + len = evbuffer_get_length(rmd->metadata); + + ret = evbuffer_add(req->output_buffer, buf, len); + if (ret != 0) + { + DPRINTF(E_LOG, L_RAOP, "Could not copy metadata for sending\n"); + return -1; + } + + ret = rtpinfo_header_add(req, rs, metadata); + if (ret < 0) + return -1; + + return 0; +} + + +/* +Audio formats + +Bit Value Type +2 0x4 PCM/8000/16/1 +3 0x8 PCM/8000/16/2 +4 0x10 PCM/16000/16/1 +5 0x20 PCM/16000/16/2 +6 0x40 PCM/24000/16/1 +7 0x80 PCM/24000/16/2 +8 0x100 PCM/32000/16/1 +9 0x200 PCM/32000/16/2 +10 0x400 PCM/44100/16/1 +11 0x800 PCM/44100/16/2 +12 0x1000 PCM/44100/24/1 +13 0x2000 PCM/44100/24/2 +14 0x4000 PCM/48000/16/1 +15 0x8000 PCM/48000/16/2 +16 0x10000 PCM/48000/24/1 +17 0x20000 PCM/48000/24/2 +18 0x40000 ALAC/44100/16/2 +19 0x80000 ALAC/44100/24/2 +20 0x100000 ALAC/48000/16/2 +21 0x200000 ALAC/48000/24/2 +22 0x400000 AAC-LC/44100/2 +23 0x800000 AAC-LC/48000/2 +24 0x1000000 AAC-ELD/44100/2 +25 0x2000000 AAC-ELD/48000/2 +26 0x4000000 AAC-ELD/16000/1 +27 0x8000000 AAC-ELD/24000/1 +28 0x10000000 OPUS/16000/1 +29 0x20000000 OPUS/24000/1 +30 0x40000000 OPUS/48000/1 +31 0x80000000 AAC-ELD/44100/1 +32 0x100000000 AAC-ELD/48000/1 +*/ + + +// {'streams': [{'audioFormat': 262144, +// 'audioMode': 'default', +// 'controlPort': 60242, +// 'ct': 2, +// 'isMedia': True, +// 'latencyMax': 88200, +// 'latencyMin': 11025, +// 'shk': b'\xdbc\x9b,\xdb\x15\x82\x19\x0b\xbf\xd3\xd0\x81\xc5\x7f7' +// b'\xaf\x7f\xb2l\xec\xca\xc8\xd0\x8d\x9d\x1d\xa8' +// b'\xda\xa2\xf7\xd0', +// 'spf': 352, +// 'supportsDynamicStreamID': True, +// 'type': 96}]} + +static int +payload_make_setup_stream(struct evrtsp_request *req, struct airplay_session *rs, void *arg) +{ + plist_t root; + plist_t streams; + plist_t stream; + uint8_t *data; + size_t len; + int ret; + + stream = plist_new_dict(); + wplist_dict_add_uint(stream, "audioFormat", 262144); // 0x40000 ALAC/44100/16/2 + wplist_dict_add_string(stream, "audioMode", "default"); + wplist_dict_add_uint(stream, "controlPort", rs->control_svc->port); + wplist_dict_add_uint(stream, "ct", 2); // Compression type, 1 LPCM, 2 ALAC, 3 AAC, 4 AAC ELD, 32 OPUS + wplist_dict_add_bool(stream, "isMedia", true); // ? + wplist_dict_add_uint(stream, "latencyMax", 88200); + wplist_dict_add_uint(stream, "latencyMin", 11025); + wplist_dict_add_data(stream, "shk", rs->shared_secret, sizeof(rs->shared_secret)); + wplist_dict_add_uint(stream, "spf", 352); // frames per packet + wplist_dict_add_uint(stream, "sr", RAOP_QUALITY_SAMPLE_RATE_DEFAULT); // sample rate + wplist_dict_add_uint(stream, "type", RAOP_RTP_PAYLOADTYPE); // RTP type, 0x60 = 96 real time, 103 buffered + wplist_dict_add_bool(stream, "supportsDynamicStreamID", false); + wplist_dict_add_uint(stream, "streamConnectionID", rs->session_id); // Hopefully fine since we have one stream per session + streams = plist_new_array(); + plist_array_append_item(streams, stream); + + root = plist_new_dict(); + plist_dict_set_item(root, "streams", streams); + ret = wplist_to_bin(&data, &len, root); + plist_free(root); + + if (ret < 0) + return -1; + + evbuffer_add(req->output_buffer, data, len); + + return 0; +} + +static int +payload_make_setpeers(struct evrtsp_request *req, struct airplay_session *rs, void *arg) +{ + plist_t item; + uint8_t *data; + size_t len; + int ret; + + plist_t root; + + // TODO also have ipv6 + root = plist_new_array(); + item = plist_new_string(rs->address); + plist_array_append_item(root, item); + item = plist_new_string(rs->local_address); + plist_array_append_item(root, item); + + ret = wplist_to_bin(&data, &len, root); + plist_free(root); + + if (ret < 0) + return -1; + + evbuffer_add(req->output_buffer, data, len); + + return 0; +} + +static int +payload_make_record(struct evrtsp_request *req, struct airplay_session *rs, void *arg) +{ + struct airplay_master_session *rms = rs->master_session; + char buf[64]; + int ret; + + evrtsp_add_header(req->output_headers, "X-Apple-ProtocolVersion", "1"); + + evrtsp_add_header(req->output_headers, "Range", "npt=0-"); + + // Start sequence: next sequence + ret = snprintf(buf, sizeof(buf), "seq=%" PRIu16 ";rtptime=%u", rms->rtp_session->seqnum, rms->rtp_session->pos); + if ((ret < 0) || (ret >= sizeof(buf))) + { + DPRINTF(E_LOG, L_RAOP, "RTP-Info too big for buffer in RECORD request\n"); + return -1; + } + evrtsp_add_header(req->output_headers, "RTP-Info", buf); + + DPRINTF(E_DBG, L_RAOP, "RTP-Info is %s\n", buf); + + return 0; +} + +// {'deviceID': '11:22:33:44:55:66', +// 'eiv': b'=o\xa0\xc24\xcd\xee\xcb9\x99~l\x140\x08\x9c', +// 'ekey': b'\x08\x90x\xa6\x0e\x87$C\x88l\xc1MS[Q\xaf', +// 'et': 0, +// 'groupContainsGroupLeader': False, +// 'groupUUID': '67EAD1FA-7EAB-4810-82F7-A9132FD2D0BB', +// 'isMultiSelectAirPlay': True, +// 'macAddress': '11:22:33:44:55:68', +// 'model': 'iPhone10,6', +// 'name': 'iPXema', +// 'osBuildVersion': '17B111', +// 'osName': 'iPhone OS', +// 'osVersion': '13.2.3', +// 'senderSupportsRelay': True, +// 'sessionUUID': '3195C737-1E6E-4487-BECB-4D287B7C7626', +// 'sourceVersion': '409.16', +// 'timingPeerInfo': {'Addresses': ['192.168.1.86', 'fe80::473:74c7:28a7:3bee'], +// 'ID': '67EAD1FA-7EAB-4810-82F7-A9132FD2D0BB', +// 'SupportsClockPortMatchingOverride': True}, +// 'timingPeerList': [{'Addresses': ['192.168.1.86', 'fe80::473:74c7:28a7:3bee'], +// 'ID': '67EAD1FA-7EAB-4810-82F7-A9132FD2D0BB', +// 'SupportsClockPortMatchingOverride': True}], +// 'timingProtocol': 'PTP'} + +static int +payload_make_setup_session(struct evrtsp_request *req, struct airplay_session *rs, void *arg) +{ + plist_t root; +// plist_t timingpeerinfo; +// plist_t timingpeerlist; + plist_t addresses; + plist_t address; + uint8_t *data; + size_t len; + int ret; + + ret = session_url_set(rs); + if (ret < 0) + { + DPRINTF(E_LOG, L_RAOP, "Could not make session url for device '%s'\n", rs->devname); + return -1; + } + + address = plist_new_string(rs->local_address); + addresses = plist_new_array(); + plist_array_append_item(addresses, address); + +/* timingpeerinfo = plist_new_dict(); + plist_dict_set_item(timingpeerinfo, "Addresses", addresses); + wplist_dict_add_string(timingpeerinfo, "ID", "67EAD1FA-7EAB-4810-82F7-A9132FD2D0BB"); + wplist_dict_add_bool(timingpeerinfo, "SupportsClockPortMatchingOverride", false); +*/ +/* timingpeerlist = plist_new_dict(); + plist_dict_set_item(timingpeerlist, "Addresses", addresses); + wplist_dict_add_string(timingpeerlist, "ID", "67EAD1FA-7EAB-4810-82F7-A9132FD2D0BB"); + wplist_dict_add_bool(timingpeerlist, "SupportsClockPortMatchingOverride", false); +*/ + root = plist_new_dict(); +// wplist_dict_add_string(root, "deviceID", "11:22:33:44:55:66"); +// wplist_dict_add_data(root, "eiv", airplay_aes_iv, sizeof(airplay_aes_iv)); +// wplist_dict_add_data(root, "ekey", airplay_aes_key, sizeof(airplay_aes_key)); +// wplist_dict_add_uint(root, "et", 0); // No encryption? +// wplist_dict_add_bool(root, "groupContainsGroupLeader", true); +// wplist_dict_add_string(root, "groupUUID", "3195C737-1E6E-4487-BECB-4D287B7C1234"); +// wplist_dict_add_bool(root, "internalBuild", false); +// wplist_dict_add_bool(root, "isMultiSelectAirPlay", true); +// wplist_dict_add_string(root, "macAddress", "00:0c:29:f6:4a:f9"); +// wplist_dict_add_string(root, "model", "iPhone10,4"); +// wplist_dict_add_string(root, "osBuildVersion", "18B92"); +// wplist_dict_add_string(root, "osName", "iPhone OS"); +// wplist_dict_add_string(root, "osVersion", "14.2"); +// wplist_dict_add_bool(root, "senderSupportsRelay", true); + wplist_dict_add_string(root, "sessionUUID", "3195C737-1E6E-4487-BECB-4D287B7C7626"); +// wplist_dict_add_string(root, "sourceVersion", "525.38.2"); +// plist_dict_set_item(root, "timingPeerInfo", timingpeerinfo); // only for PTP timing? +// plist_dict_set_item(root, "timingPeerList", timingpeerlist); // only for PTP timing? + wplist_dict_add_uint(root, "timingPort", rs->timing_svc->port); + wplist_dict_add_string(root, "timingProtocol", "NTP"); // If set to "None" then an ATV4 will not respond to stream SETUP request +// wplist_dict_add_string(root, "timingProtocol", "None"); + + ret = wplist_to_bin(&data, &len, root); + plist_free(root); + + if (ret < 0) + return -1; + + evbuffer_add(req->output_buffer, data, len); + + return 0; +} + +/* +The purpose of auth-setup is to authenticate the device and to exchange keys +for encryption. We don't do that, but some AirPlay 2 speakers (Sonos beam, +Airport Express fw 7.8) require this step anyway, otherwise we get a 403 to +our ANNOUNCE. So we do it with a flag for no encryption, and without actually +authenticating the device. + +Good to know (source Apple's MFi Accessory Interface Specification): +- Curve25519 Elliptic-Curve Diffie-Hellman technology for key exchange +- RSA for signing and verifying and AES-128 in counter mode for encryption +- We start by sending a Curve25519 public key + no encryption flag +- The device responds with public key, MFi certificate and a signature, which + is created by the device signing the two public keys with its RSA private + key and then encrypting the result with the AES master key derived from the + Curve25519 shared secret (generated from device private key and our public + key) +- The AES key derived from the Curve25519 shared secret can then be used to + encrypt future content +- New keys should be generated for each authentication attempt, but we don't + do that because we don't really use this + it adds a libsodium dependency + +Since we don't do auth or encryption, we currently just ignore the reponse. +*/ +/* +static int +payload_make_auth_setup(struct evrtsp_request *req, struct airplay_session *rs, void *arg) +{ + if (!(rs->supports_post && rs->supports_auth_setup)) + return 1; // skip this request + + // Flag for no encryption. 0x10 may mean encryption. + evbuffer_add(req->output_buffer, "\x01", 1); + + evbuffer_add(req->output_buffer, airplay_auth_setup_pubkey, sizeof(airplay_auth_setup_pubkey) - 1); + + return 0; +} +*/ +static int +payload_make_pin_start(struct evrtsp_request *req, struct airplay_session *rs, void *arg) +{ + DPRINTF(E_LOG, L_RAOP, "Starting device pairing for '%s', go to the web interface and enter PIN\n", rs->devname); + return 0; +} + +static int +payload_make_pair_generic(int step, struct evrtsp_request *req, struct airplay_session *rs) +{ + uint8_t *body; + uint32_t len; + const char *errmsg; + + switch (step) + { + case 1: + body = pair_setup_request1(&len, rs->pair_setup_ctx); + errmsg = pair_setup_errmsg(rs->pair_setup_ctx); + break; + case 2: + body = pair_setup_request2(&len, rs->pair_setup_ctx); + errmsg = pair_setup_errmsg(rs->pair_setup_ctx); + break; + case 3: + body = pair_setup_request3(&len, rs->pair_setup_ctx); + errmsg = pair_setup_errmsg(rs->pair_setup_ctx); + break; + case 4: + body = pair_verify_request1(&len, rs->pair_verify_ctx); + errmsg = pair_verify_errmsg(rs->pair_verify_ctx); + break; + case 5: + body = pair_verify_request2(&len, rs->pair_verify_ctx); + errmsg = pair_verify_errmsg(rs->pair_verify_ctx); + break; + default: + body = NULL; + errmsg = "Bug! Bad step number"; + } + + if (!body) + { + DPRINTF(E_LOG, L_RAOP, "Verification step %d request error: %s\n", step, errmsg); + return -1; + } + + evbuffer_add(req->output_buffer, body, len); + free(body); + + // Required!! + if (rs->pair_type == PAIR_HOMEKIT_NORMAL) + evrtsp_add_header(req->output_headers, "X-Apple-HKP", "3"); + else if (rs->pair_type == PAIR_HOMEKIT_TRANSIENT) + evrtsp_add_header(req->output_headers, "X-Apple-HKP", "4"); + + return 0; +} + +static int +payload_make_pair_setup1(struct evrtsp_request *req, struct airplay_session *rs, void *arg) +{ + char *pin = arg; + + if (pin) + rs->pair_type = PAIR_HOMEKIT_NORMAL; + + rs->pair_setup_ctx = pair_setup_new(rs->pair_type, pin, pair_device_id); + if (!rs->pair_setup_ctx) + { + DPRINTF(E_LOG, L_RAOP, "Out of memory for verification setup context\n"); + return -1; + } + + rs->state = AIRPLAY_STATE_PASSWORD; + + return payload_make_pair_generic(1, req, rs); +} + +static int +payload_make_pair_setup2(struct evrtsp_request *req, struct airplay_session *rs, void *arg) +{ + return payload_make_pair_generic(2, req, rs); +} + +static int +payload_make_pair_setup3(struct evrtsp_request *req, struct airplay_session *rs, void *arg) +{ + return payload_make_pair_generic(3, req, rs); +} + +static int +payload_make_pair_verify1(struct evrtsp_request *req, struct airplay_session *rs, void *arg) +{ + struct output_device *device; + + device = outputs_device_get(rs->device_id); + if (!device) + return -1; + + rs->pair_verify_ctx = pair_verify_new(rs->pair_type, device->auth_key, pair_device_id); + if (!rs->pair_verify_ctx) + { + DPRINTF(E_LOG, L_RAOP, "Out of memory for verification verify context\n"); + return -1; + } + + return payload_make_pair_generic(4, req, rs); +} + +static int +payload_make_pair_verify2(struct evrtsp_request *req, struct airplay_session *rs, void *arg) +{ + return payload_make_pair_generic(5, req, rs); +} + + +/* ------------------------------ Session startup --------------------------- */ + +static int +device_connect(struct airplay_session *rs, unsigned short port, int type) +{ + int len; + int fd; + int ret; + + DPRINTF(E_DBG, L_RAOP, "Connecting to %s (family=%d), port %u\n", rs->address, rs->family, port); + +#ifdef SOCK_CLOEXEC + fd = socket(rs->sa.ss.ss_family, type | SOCK_CLOEXEC, 0); +#else + fd = socket(rs->sa.ss.ss_family, type, 0); +#endif + if (fd < 0) + { + DPRINTF(E_LOG, L_RAOP, "Could not create socket: %s\n", strerror(errno)); + return -1; + } + + switch (rs->sa.ss.ss_family) + { + case AF_INET: + rs->sa.sin.sin_port = htons(port); + len = sizeof(rs->sa.sin); + break; + + case AF_INET6: + rs->sa.sin6.sin6_port = htons(port); + len = sizeof(rs->sa.sin6); + break; + + default: + DPRINTF(E_WARN, L_RAOP, "Unknown family %d\n", rs->sa.ss.ss_family); + close(fd); + return -1; + } + + ret = connect(fd, &rs->sa.sa, len); + if (ret < 0) + { + DPRINTF(E_LOG, L_RAOP, "connect() to [%s]:%u failed: %s\n", rs->address, port, strerror(errno)); + close(fd); + return -1; + } + + return fd; +} + +static void +start_failure(struct airplay_session *rs) +{ + // Tear down the connection + sequence_start(AIRPLAY_SEQ_FAILURE, rs, NULL, "startup_failure"); +} + +static void +start_retry(struct airplay_session *rs) +{ + struct output_device *device; + int callback_id = rs->callback_id; + + device = outputs_device_get(rs->device_id); + if (!device || !rs->session) + { + session_failure(rs); + return; + } + + // Some devices don't seem to work with ipv6, so if the error wasn't a hard + // failure (bad password) we fall back to ipv4 and flag device as bad for ipv6 + if (rs->family != AF_INET6 || (rs->state & AIRPLAY_STATE_F_FAILED)) + { + session_failure(rs); + return; + } + + // This flag is permanent and will not be overwritten by mdns advertisements + device->v6_disabled = 1; + + // Drop session, try again with ipv4 + session_cleanup(rs); + airplay_device_start(device, callback_id); +} + + +/* ---------------------------- RTSP response handlers ---------------------- */ + +static enum airplay_seq_type +response_handler_pin_start(struct evrtsp_request *req, struct airplay_session *rs) +{ + rs->state = AIRPLAY_STATE_PASSWORD; + + return AIRPLAY_SEQ_CONTINUE; // TODO before we reported failure since device is locked +} + +static enum airplay_seq_type +response_handler_record(struct evrtsp_request *req, struct airplay_session *rs) +{ + const char *param; + + /* Audio latency */ + param = evrtsp_find_header(req->input_headers, "Audio-Latency"); + if (!param) + DPRINTF(E_INFO, L_RAOP, "RECORD reply from '%s' did not have an Audio-Latency header\n", rs->devname); + else + DPRINTF(E_DBG, L_RAOP, "RAOP audio latency is %s\n", param); + + rs->state = AIRPLAY_STATE_RECORD; + + return AIRPLAY_SEQ_CONTINUE; +} + +static enum airplay_seq_type +response_handler_setup_stream(struct evrtsp_request *req, struct airplay_session *rs) +{ + plist_t response; + plist_t streams; + plist_t stream; + plist_t item; + uint64_t uintval; + int ret; + + ret = wplist_from_evbuf(&response, req->input_buffer); + if (ret < 0) + { + DPRINTF(E_LOG, L_RAOP, "Could not parse plist from '%s'\n", rs->devname); + return AIRPLAY_SEQ_ABORT; + } + + streams = plist_dict_get_item(response, "streams"); + if (!streams) + { + DPRINTF(E_LOG, L_RAOP, "Could not find streams item in response from '%s'\n", rs->devname); + goto error; + } + + stream = plist_array_get_item(streams, 0); + if (!stream) + { + DPRINTF(E_LOG, L_RAOP, "Could not find stream item in response from '%s'\n", rs->devname); + goto error; + } + + item = plist_dict_get_item(stream, "dataPort"); + if (item) + { + plist_get_uint_val(item, &uintval); + rs->data_port = uintval; + } + + item = plist_dict_get_item(stream, "controlPort"); + if (item) + { + plist_get_uint_val(item, &uintval); + rs->control_port = uintval; + } + + if (rs->data_port == 0 || rs->control_port == 0) + { + DPRINTF(E_LOG, L_RAOP, "Missing port number in reply from '%s' (d=%u, c=%u)\n", rs->devname, rs->data_port, rs->control_port); + goto error; + } + + DPRINTF(E_DBG, L_RAOP, "Negotiated AirTunes v2 UDP streaming session %s; ports d=%u c=%u t=%u e=%u\n", rs->session, rs->data_port, rs->control_port, rs->timing_port, rs->events_port); + + rs->server_fd = device_connect(rs, rs->data_port, SOCK_DGRAM); + if (rs->server_fd < 0) + { + DPRINTF(E_WARN, L_RAOP, "Could not connect to data port\n"); + goto error; + } + + // Reverse connection, used to receive playback events from device + rs->events_fd = device_connect(rs, rs->events_port, SOCK_STREAM); + if (rs->events_fd < 0) + { + DPRINTF(E_WARN, L_RAOP, "Could not connect to '%s' events port %u, proceeding anyway\n", rs->devname, rs->events_port); + } + else + { + struct event *ev = event_new(evbase_player, rs->events_fd, EV_READ | EV_PERSIST, event_channel_cb, rs); // TODO, possibly use evrtsp instead + event_add(ev, NULL); + } + + rs->state = AIRPLAY_STATE_SETUP; + + plist_free(response); + return AIRPLAY_SEQ_CONTINUE; + + error: + plist_free(response); + return AIRPLAY_SEQ_ABORT; +} + +static enum airplay_seq_type +response_handler_volume_start(struct evrtsp_request *req, struct airplay_session *rs) +{ + int ret; + + ret = airplay_metadata_startup_send(rs); // TODO Should this be added to the startup sequence? + if (ret < 0) + return AIRPLAY_SEQ_ABORT; + + return AIRPLAY_SEQ_CONTINUE; +} + +static enum airplay_seq_type +response_handler_setup_session(struct evrtsp_request *req, struct airplay_session *rs) +{ + plist_t response; + plist_t item; + uint64_t uintval; + int ret; + + ret = wplist_from_evbuf(&response, req->input_buffer); + if (ret < 0) + { + DPRINTF(E_LOG, L_RAOP, "Could not parse plist from '%s'\n", rs->devname); + return AIRPLAY_SEQ_ABORT; + } + + item = plist_dict_get_item(response, "eventPort"); + if (item) + { + plist_get_uint_val(item, &uintval); + rs->events_port = uintval; + } + + item = plist_dict_get_item(response, "timingPort"); + if (item) + { + plist_get_uint_val(item, &uintval); + rs->timing_port = uintval; + } + + if (rs->events_port == 0) + { + DPRINTF(E_LOG, L_RAOP, "SETUP reply is missing event port\n"); + goto error; + } + + plist_free(response); + return AIRPLAY_SEQ_CONTINUE; + + error: + plist_free(response); + return AIRPLAY_SEQ_ABORT; +} + +static enum airplay_seq_type +response_handler_flush(struct evrtsp_request *req, struct airplay_session *rs) +{ + rs->state = AIRPLAY_STATE_CONNECTED; + return AIRPLAY_SEQ_CONTINUE; +} + +static enum airplay_seq_type +response_handler_teardown(struct evrtsp_request *req, struct airplay_session *rs) +{ + rs->state = AIRPLAY_STATE_STOPPED; + return AIRPLAY_SEQ_CONTINUE; +} + +static enum airplay_seq_type +response_handler_teardown_failure(struct evrtsp_request *req, struct airplay_session *rs) +{ + if (rs->state != AIRPLAY_STATE_PASSWORD) + rs->state = AIRPLAY_STATE_FAILED; + return AIRPLAY_SEQ_CONTINUE; +} + +static enum airplay_seq_type +response_handler_options_generic(struct evrtsp_request *req, struct airplay_session *rs) +{ + struct output_device *device; + const char *param; + int ret; + + if ((req->response_code != RTSP_OK) && (req->response_code != RTSP_UNAUTHORIZED) && (req->response_code != RTSP_FORBIDDEN)) + { + DPRINTF(E_LOG, L_RAOP, "OPTIONS request failed '%s' (%s): %d %s\n", rs->devname, rs->address, req->response_code, req->response_code_line); + goto error; + } + + if (req->response_code == RTSP_UNAUTHORIZED) + { + if (rs->req_has_auth) + { + DPRINTF(E_LOG, L_RAOP, "Bad password for device '%s' (%s)\n", rs->devname, rs->address); + rs->state = AIRPLAY_STATE_PASSWORD; + goto error; + } + + ret = response_header_auth_parse(rs, req); + if (ret < 0) + { + goto error; + } + + return AIRPLAY_SEQ_START_RERUN; + } + + if (req->response_code == RTSP_FORBIDDEN) + { + device = outputs_device_get(rs->device_id); + if (!device) + goto error; + + device->requires_auth = 1; + + return AIRPLAY_SEQ_PIN_START; + } + + param = evrtsp_find_header(req->input_headers, "Public"); + if (param) + rs->supports_post = (strstr(param, "POST") != NULL); + else + DPRINTF(E_DBG, L_RAOP, "Could not find 'Public' header in OPTIONS reply from '%s' (%s)\n", rs->devname, rs->address); + + rs->state = AIRPLAY_STATE_OPTIONS; + + return AIRPLAY_SEQ_CONTINUE; + + error: + return AIRPLAY_SEQ_ABORT; +} + +static enum airplay_seq_type +response_handler_options_probe(struct evrtsp_request *req, struct airplay_session *rs) +{ + return response_handler_options_generic(req, rs); +} + +static enum airplay_seq_type +response_handler_options_start(struct evrtsp_request *req, struct airplay_session *rs) +{ + enum airplay_seq_type seq_type; + + seq_type = response_handler_options_generic(req, rs); + if (seq_type != AIRPLAY_SEQ_CONTINUE) + return seq_type; + + return AIRPLAY_SEQ_START_AP2; +} + +static enum airplay_seq_type +response_handler_pair_generic(int step, struct evrtsp_request *req, struct airplay_session *rs) +{ + uint8_t *response; + const char *errmsg; + size_t len; + int ret; + + response = evbuffer_pullup(req->input_buffer, -1); + len = evbuffer_get_length(req->input_buffer); + + switch (step) + { + case 1: + ret = pair_setup_response1(rs->pair_setup_ctx, response, len); + errmsg = pair_setup_errmsg(rs->pair_setup_ctx); + break; + case 2: + ret = pair_setup_response2(rs->pair_setup_ctx, response, len); + errmsg = pair_setup_errmsg(rs->pair_setup_ctx); + break; + case 3: + ret = pair_setup_response3(rs->pair_setup_ctx, response, len); + errmsg = pair_setup_errmsg(rs->pair_setup_ctx); + break; + case 4: + ret = pair_verify_response1(rs->pair_verify_ctx, response, len); + errmsg = pair_verify_errmsg(rs->pair_verify_ctx); + break; + case 5: + ret = pair_verify_response2(rs->pair_verify_ctx, response, len); + errmsg = pair_verify_errmsg(rs->pair_verify_ctx); + break; + default: + ret = -1; + errmsg = "Bug! Bad step number"; + } + + if (ret < 0) + { + DPRINTF(E_LOG, L_RAOP, "Pairing step %d response from '%s' error: %s\n", step, rs->devname, errmsg); + DHEXDUMP(E_DBG, L_RAOP, response, len, "Raw response"); + return AIRPLAY_SEQ_ABORT; + } + + return AIRPLAY_SEQ_CONTINUE; +} + +static enum airplay_seq_type +response_handler_pair_setup1(struct evrtsp_request *req, struct airplay_session *rs) +{ + struct output_device *device; + + if (rs->pair_type == PAIR_HOMEKIT_TRANSIENT && req->response_code == RTSP_CONNECTION_AUTH_REQUIRED) + { + device = outputs_device_get(rs->device_id); + if (!device) + return AIRPLAY_SEQ_ABORT; + + device->requires_auth = 1; // FIXME might be reset by mdns announcement + rs->pair_type = PAIR_HOMEKIT_NORMAL; + + return AIRPLAY_SEQ_PIN_START; + } + + return response_handler_pair_generic(1, req, rs); +} + +static enum airplay_seq_type +response_handler_pair_setup2(struct evrtsp_request *req, struct airplay_session *rs) +{ + enum airplay_seq_type seq_type; + const uint8_t *shared_secret; + size_t shared_secret_len; + int ret; + + seq_type = response_handler_pair_generic(2, req, rs); + if (seq_type != AIRPLAY_SEQ_CONTINUE) + return seq_type; + + if (rs->pair_type != PAIR_HOMEKIT_TRANSIENT) + return seq_type; + + ret = pair_setup_result(NULL, &shared_secret, &shared_secret_len, rs->pair_setup_ctx); + if (ret < 0) + { + DPRINTF(E_LOG, L_RAOP, "Transient setup result error: %s\n", pair_setup_errmsg(rs->pair_setup_ctx)); + goto error; + } + + if (shared_secret_len < sizeof(rs->shared_secret)) // We expect 64 bytes, and rs->shared_secret is 32 bytes + { + DPRINTF(E_LOG, L_RAOP, "Transient setup result error: Unexpected key length (%zu)\n", shared_secret_len); + goto error; + } + + // Copy the first 32 bytes while be used later for encrypting audio payload + memcpy(rs->shared_secret, shared_secret, sizeof(rs->shared_secret)); + + rs->control_cipher_ctx = pair_cipher_new(rs->pair_type, 0, shared_secret, shared_secret_len); + if (!rs->control_cipher_ctx) + { + DPRINTF(E_LOG, L_RAOP, "Could not create control ciphering context\n"); + goto error; + } + + rs->events_cipher_ctx = pair_cipher_new(rs->pair_type, 1, shared_secret, shared_secret_len); + if (!rs->events_cipher_ctx) + { + DPRINTF(E_LOG, L_RAOP, "Could not create events ciphering context\n"); + goto error; + } + + evrtsp_connection_set_ciphercb(rs->ctrl, rtsp_cipher, rs); + + DPRINTF(E_INFO, L_RAOP, "Transient setup of '%s' completed succesfully, now using encrypted mode\n", rs->devname); + + rs->state = AIRPLAY_STATE_STARTUP; + + return AIRPLAY_SEQ_CONTINUE; + + error: + rs->state = AIRPLAY_STATE_FAILED; + return AIRPLAY_SEQ_ABORT; +} + +static enum airplay_seq_type +response_handler_pair_setup3(struct evrtsp_request *req, struct airplay_session *rs) +{ + struct output_device *device; + const char *authorization_key; + enum airplay_seq_type seq_type; + int ret; + + seq_type = response_handler_pair_generic(3, req, rs); + if (seq_type != AIRPLAY_SEQ_CONTINUE) + return seq_type; + + ret = pair_setup_result(&authorization_key, NULL, NULL, rs->pair_setup_ctx); + if (ret < 0) + { + DPRINTF(E_LOG, L_RAOP, "Pair setup result error: %s\n", pair_setup_errmsg(rs->pair_setup_ctx)); + return AIRPLAY_SEQ_ABORT; + } + + DPRINTF(E_LOG, L_RAOP, "Pair setup stage complete, saving authorization key\n"); + + device = outputs_device_get(rs->device_id); + if (!device) + return AIRPLAY_SEQ_ABORT; + + free(device->auth_key); + device->auth_key = strdup(authorization_key); + + // A blocking db call... :-~ + db_speaker_save(device); + + // No longer AIRPLAY_STATE_PASSWORD + rs->state = AIRPLAY_STATE_STOPPED; + + return AIRPLAY_SEQ_CONTINUE; +} + +static enum airplay_seq_type +response_handler_pair_verify1(struct evrtsp_request *req, struct airplay_session *rs) +{ + struct output_device *device; + enum airplay_seq_type seq_type; + + seq_type = response_handler_pair_generic(4, req, rs); + if (seq_type != AIRPLAY_SEQ_CONTINUE) + { + rs->state = AIRPLAY_STATE_PASSWORD; + + device = outputs_device_get(rs->device_id); + if (!device) + return AIRPLAY_SEQ_ABORT; + + // Clear auth_key, the device did not accept it + free(device->auth_key); + device->auth_key = NULL; + + return AIRPLAY_SEQ_ABORT; + } + + return seq_type; +} + +static enum airplay_seq_type +response_handler_pair_verify2(struct evrtsp_request *req, struct airplay_session *rs) +{ + struct output_device *device; + enum airplay_seq_type seq_type; + const uint8_t *shared_secret; + size_t shared_secret_len; + int ret; + + seq_type = response_handler_pair_generic(5, req, rs); + if (seq_type != AIRPLAY_SEQ_CONTINUE) + goto error; + + ret = pair_verify_result(&shared_secret, &shared_secret_len, rs->pair_verify_ctx); + if (ret < 0) + { + DPRINTF(E_LOG, L_RAOP, "Pair verify result error: %s\n", pair_verify_errmsg(rs->pair_verify_ctx)); + goto error; + } + + if (sizeof(rs->shared_secret) != shared_secret_len) + { + DPRINTF(E_LOG, L_RAOP, "Pair verify result error: Unexpected key length (%zu)\n", shared_secret_len); + goto error; + } + + memcpy(rs->shared_secret, shared_secret, shared_secret_len); + + rs->control_cipher_ctx = pair_cipher_new(rs->pair_type, 0, rs->shared_secret, sizeof(rs->shared_secret)); + if (!rs->control_cipher_ctx) + { + DPRINTF(E_LOG, L_RAOP, "Could not create control ciphering context\n"); + goto error; + } + + rs->events_cipher_ctx = pair_cipher_new(rs->pair_type, 1, rs->shared_secret, sizeof(rs->shared_secret)); + if (!rs->events_cipher_ctx) + { + DPRINTF(E_LOG, L_RAOP, "Could not create events ciphering context\n"); + goto error; + } + + evrtsp_connection_set_ciphercb(rs->ctrl, rtsp_cipher, rs); + + DPRINTF(E_INFO, L_RAOP, "Pairing of '%s' completed succesfully, now using encrypted mode\n", rs->devname); + + rs->state = AIRPLAY_STATE_STARTUP; + + return AIRPLAY_SEQ_CONTINUE; + + error: + device = outputs_device_get(rs->device_id); + if (!device) + return AIRPLAY_SEQ_ABORT; + + // Clear auth_key, the device did not accept it, or some other unexpected error + free(device->auth_key); + device->auth_key = NULL; + + rs->state = AIRPLAY_STATE_PASSWORD; + + return AIRPLAY_SEQ_ABORT; +} + + +/* ---------------------- Request/response sequence control ----------------- */ + +/* + * Request queueing HOWTO + * + * Sending: + * - increment rs->reqs_in_flight + * - set evrtsp connection closecb to NULL + * + * Request callback: + * - decrement rs->reqs_in_flight first thing, even if the callback is + * called for error handling (req == NULL or HTTP error code) + * - if rs->reqs_in_flight == 0, setup evrtsp connection closecb + * + * When a request fails, the whole RAOP session is declared failed and + * torn down by calling session_failure(), even if there are requests + * queued on the evrtsp connection. There is no reason to think pending + * requests would work out better than the one that just failed and recovery + * would be tricky to get right. + * + * evrtsp behaviour with queued requests: + * - request callback is called with req == NULL to indicate a connection + * error; if there are several requests queued on the connection, this can + * happen for each request if the connection isn't destroyed + * - the connection is reset, and the closecb is called if the connection was + * previously connected. There is no closecb set when there are requests in + * flight + */ + +static struct airplay_seq_definition airplay_seq_definition[] = +{ + { AIRPLAY_SEQ_START, NULL, start_retry }, + { AIRPLAY_SEQ_START_RERUN, NULL, start_retry }, + { AIRPLAY_SEQ_START_AP2, session_connected, start_failure }, + { AIRPLAY_SEQ_PROBE, session_success, session_failure }, + { AIRPLAY_SEQ_FLUSH, session_status, session_failure }, + { AIRPLAY_SEQ_STOP, session_success, session_failure }, + { AIRPLAY_SEQ_FAILURE, session_success, session_failure}, + { AIRPLAY_SEQ_PIN_START, session_success, session_failure }, + { AIRPLAY_SEQ_SEND_VOLUME, session_status, session_failure }, + { AIRPLAY_SEQ_SEND_TEXT, NULL, session_failure }, + { AIRPLAY_SEQ_SEND_PROGRESS, NULL, session_failure }, + { AIRPLAY_SEQ_SEND_ARTWORK, NULL, session_failure }, + { AIRPLAY_SEQ_PAIR_SETUP, session_pair_success, session_failure }, + { AIRPLAY_SEQ_PAIR_VERIFY, session_pair_success, session_failure }, + { AIRPLAY_SEQ_PAIR_TRANSIENT, session_pair_success, session_failure }, + { AIRPLAY_SEQ_FEEDBACK, NULL, session_failure }, +}; + +// The size of the second array dimension MUST at least be the size of largest +// sequence + 1, because then we can count on a zero terminator when iterating +static struct airplay_seq_request airplay_seq_request[][7] = +{ + { + // response_handler_options() will determine appropriate sequence to continue with based on device response + { AIRPLAY_SEQ_START, "OPTIONS", EVRTSP_REQ_OPTIONS, NULL, response_handler_options_start, NULL, "*", true }, + }, + { + { AIRPLAY_SEQ_START_RERUN, "OPTIONS (re-run)", EVRTSP_REQ_OPTIONS, NULL, response_handler_options_start, NULL, "*", false }, + }, + { +// { AIRPLAY_SEQ_START_AP2, "auth-setup", EVRTSP_REQ_POST, payload_make_auth_setup, NULL, "application/octet-stream", "/auth-setup", true }, + { AIRPLAY_SEQ_START_AP2, "SETUP (session)", EVRTSP_REQ_SETUP, payload_make_setup_session, response_handler_setup_session, "application/x-apple-binary-plist", NULL, false }, + { AIRPLAY_SEQ_START_AP2, "SETPEERS", EVRTSP_REQ_SETPEERS, payload_make_setpeers, NULL, "/peer-list-changed", NULL, false }, + { AIRPLAY_SEQ_START_AP2, "SETUP (stream)", EVRTSP_REQ_SETUP, payload_make_setup_stream, response_handler_setup_stream, "application/x-apple-binary-plist", NULL, false }, + { AIRPLAY_SEQ_START_AP2, "SET_PARAMETER (volume)", EVRTSP_REQ_SET_PARAMETER, payload_make_set_volume, response_handler_volume_start, "text/parameters", NULL, true }, + { AIRPLAY_SEQ_START_AP2, "RECORD", EVRTSP_REQ_RECORD, payload_make_record, response_handler_record, NULL, NULL, false }, + }, + { + { AIRPLAY_SEQ_PROBE, "OPTIONS (probe)", EVRTSP_REQ_OPTIONS, NULL, response_handler_options_probe, NULL, "*", true }, + }, + { + { AIRPLAY_SEQ_FLUSH, "FLUSH", EVRTSP_REQ_FLUSH, payload_make_flush, response_handler_flush, NULL, NULL, false }, + }, + { + { AIRPLAY_SEQ_STOP, "TEARDOWN", EVRTSP_REQ_TEARDOWN, payload_make_teardown, response_handler_teardown, NULL, NULL, true }, + }, + { + { AIRPLAY_SEQ_FAILURE, "TEARDOWN (failure)", EVRTSP_REQ_TEARDOWN, payload_make_teardown, response_handler_teardown_failure, NULL, NULL, false }, + }, + { + { AIRPLAY_SEQ_PIN_START, "PIN start", EVRTSP_REQ_POST, payload_make_pin_start, response_handler_pin_start, NULL, "/pair-pin-start", false }, + }, + { + { AIRPLAY_SEQ_SEND_VOLUME, "SET_PARAMETER (volume)", EVRTSP_REQ_SET_PARAMETER, payload_make_set_volume, NULL, "text/parameters", NULL, true }, + }, + { + { AIRPLAY_SEQ_SEND_TEXT, "SET_PARAMETER (text)", EVRTSP_REQ_SET_PARAMETER, payload_make_send_text, NULL, "application/x-dmap-tagged", NULL, true }, + }, + { + { AIRPLAY_SEQ_SEND_PROGRESS, "SET_PARAMETER (progress)", EVRTSP_REQ_SET_PARAMETER, payload_make_send_progress, NULL, "text/parameters", NULL, true }, + }, + { + { AIRPLAY_SEQ_SEND_ARTWORK, "SET_PARAMETER (artwork)", EVRTSP_REQ_SET_PARAMETER, payload_make_send_artwork, NULL, NULL, NULL, true }, + }, + { + { AIRPLAY_SEQ_PAIR_SETUP, "pair setup 1", EVRTSP_REQ_POST, payload_make_pair_setup1, response_handler_pair_setup1, "application/octet-stream", "/pair-setup", false }, + { AIRPLAY_SEQ_PAIR_SETUP, "pair setup 2", EVRTSP_REQ_POST, payload_make_pair_setup2, response_handler_pair_setup2, "application/octet-stream", "/pair-setup", false }, + { AIRPLAY_SEQ_PAIR_SETUP, "pair setup 3", EVRTSP_REQ_POST, payload_make_pair_setup3, response_handler_pair_setup3, "application/octet-stream", "/pair-setup", false }, + }, + { + // Proceed on error is true because we want to delete the device key in the response handler if the verification fails + { AIRPLAY_SEQ_PAIR_VERIFY, "pair verify 1", EVRTSP_REQ_POST, payload_make_pair_verify1, response_handler_pair_verify1, "application/octet-stream", "/pair-verify", true }, + { AIRPLAY_SEQ_PAIR_VERIFY, "pair verify 2", EVRTSP_REQ_POST, payload_make_pair_verify2, response_handler_pair_verify2, "application/octet-stream", "/pair-verify", false }, + }, + { + // Some devices (i.e. my ATV4) gives a 470 when trying transient, so we proceed on that so the handler can trigger PIN setup sequence + { AIRPLAY_SEQ_PAIR_TRANSIENT, "pair setup 1", EVRTSP_REQ_POST, payload_make_pair_setup1, response_handler_pair_setup1, "application/octet-stream", "/pair-setup", true }, + { AIRPLAY_SEQ_PAIR_TRANSIENT, "pair setup 2", EVRTSP_REQ_POST, payload_make_pair_setup2, response_handler_pair_setup2, "application/octet-stream", "/pair-setup", false }, + }, + { + { AIRPLAY_SEQ_FEEDBACK, "POST /feedback", EVRTSP_REQ_POST, NULL, NULL, NULL, "/feedback", true }, + }, +}; + + +static void +sequence_continue_cb(struct evrtsp_request *req, void *arg) +{ + struct airplay_seq_ctx *seq_ctx = arg; + struct airplay_seq_request *cur_request = seq_ctx->cur_request; + struct airplay_session *rs = seq_ctx->session; + enum airplay_seq_type seq_type; + + rs->reqs_in_flight--; + if (!rs->reqs_in_flight) + evrtsp_connection_set_closecb(rs->ctrl, rtsp_close_cb, rs); + + if (!req) + { + DPRINTF(E_LOG, L_RAOP, "No response to %s from '%s'\n", cur_request->name, rs->devname); + goto error; + } + + if (req->response_code != RTSP_OK) + { + if (!cur_request->proceed_on_rtsp_not_ok) + { + DPRINTF(E_LOG, L_RAOP, "Response to %s from '%s' was negative, aborting (%d %s)\n", cur_request->name, rs->devname, req->response_code, req->response_code_line); + goto error; + } + + DPRINTF(E_WARN, L_RAOP, "Response to %s from '%s' was negative, proceeding anyway (%d %s)\n", cur_request->name, rs->devname, req->response_code, req->response_code_line); + } + + // We don't check that the reply CSeq matches the request CSeq, because some + // targets like Reflector and AirFoil don't return the CSeq according to the + // rtsp spec. And the CSeq is not really important anyway. + + if (cur_request->response_handler) + { + seq_type = cur_request->response_handler(req, rs); + if (seq_type != AIRPLAY_SEQ_CONTINUE) + { + if (seq_type == AIRPLAY_SEQ_ABORT) + goto error; + + // Handler wanted to start a new sequence + sequence_start(seq_type, seq_ctx->session, seq_ctx->payload_make_arg, seq_ctx->log_caller); + free(seq_ctx); + return; + } + } + + seq_ctx->cur_request++; + + if (seq_ctx->cur_request->name) + { + sequence_continue(seq_ctx); + return; + } + + if (seq_ctx->on_success) + seq_ctx->on_success(rs); + + free(seq_ctx); + return; + + error: + if (seq_ctx->on_error) + seq_ctx->on_error(rs); + + free(seq_ctx); +} + +static void +sequence_continue(struct airplay_seq_ctx *seq_ctx) +{ + struct airplay_session *rs = seq_ctx->session; + struct airplay_seq_request *cur_request = seq_ctx->cur_request; + struct evrtsp_request *req = NULL; + const char *uri; + int ret; + + req = evrtsp_request_new(sequence_continue_cb, seq_ctx); + if (!req) + goto error; + + ret = request_headers_add(req, rs, cur_request->rtsp_type); + if (ret < 0) + goto error; + + if (cur_request->content_type) + evrtsp_add_header(req->output_headers, "Content-Type", cur_request->content_type); + + if (cur_request->payload_make) + { + ret = cur_request->payload_make(req, rs, seq_ctx->payload_make_arg); + if (ret > 0) // Skip to next request in sequence, if none -> error + { + seq_ctx->cur_request++; + if (!seq_ctx->cur_request->name) + { + DPRINTF(E_LOG, L_RAOP, "Bug! payload_make signaled skip request, but there is nothing to skip to\n"); + goto error; + } + + evrtsp_request_free(req); + sequence_continue(seq_ctx); + return; + } + else if (ret < 0) + goto error; + } + + uri = (cur_request->uri) ? cur_request->uri : rs->session_url; + + DPRINTF(E_DBG, L_RAOP, "%s: Sending %s to '%s'\n", seq_ctx->log_caller, cur_request->name, rs->devname); + + ret = evrtsp_make_request(rs->ctrl, req, cur_request->rtsp_type, uri); + if (ret < 0) + goto error; + + evrtsp_connection_set_closecb(rs->ctrl, NULL, NULL); + + rs->reqs_in_flight++; + + return; + + error: + DPRINTF(E_LOG, L_RAOP, "%s: Error sending %s to '%s'\n", seq_ctx->log_caller, cur_request->name, rs->devname); + + if (req) + evrtsp_request_free(req); + + // Sets status to FAILED, gives status to player and frees session. Must be + // deferred, otherwise sequence_start() could invalidate the session, meaning + // any dereference of the session by the caller after sequence_start() would + // segfault. + deferred_session_failure(rs); + + free(seq_ctx); +} + +// All errors that may occur during a sequence are called back async +static void +sequence_start(enum airplay_seq_type seq_type, struct airplay_session *rs, void *arg, const char *log_caller) +{ + struct airplay_seq_ctx *seq_ctx; + + CHECK_NULL(L_RAOP, seq_ctx = calloc(1, sizeof(struct airplay_seq_ctx))); + + seq_ctx->session = rs; + seq_ctx->cur_request = &airplay_seq_request[seq_type][0]; // First step of the sequence + seq_ctx->on_success = airplay_seq_definition[seq_type].on_success; + seq_ctx->on_error = airplay_seq_definition[seq_type].on_error; + seq_ctx->payload_make_arg = arg; + seq_ctx->log_caller = log_caller; + + sequence_continue(seq_ctx); // Ownership transferred +} + + +/* ---------------- Airplay devices discovery - mDNS callback --------------- */ +/* Thread: main (mdns) */ + +static int +features_parse(struct keyval *features_kv, const char *fs1, const char *fs2, const char *name) +{ + uint64_t features; + int i, j; + + if (safe_hextou32(fs1, (uint32_t *)&features) < 0 || safe_hextou32(fs2, ((uint32_t *)&features) + 1) < 0) + { + DPRINTF(E_LOG, L_RAOP, "AirPlay '%s': unexpected features field in TXT record!\n", name); + return -1; + } + + DPRINTF(E_DBG, L_RAOP, "Parsing features flags from AirPlay '%s': %s (%" PRIu64 ")\n", name, fs1, features); + + // Walk through the bits + for (i = 0; i < (sizeof(features) * CHAR_BIT); i++) + { + if (((features >> i) & 0x01) == 0) + continue; + + // Check if we have it in the features map + for (j = 0; j < ARRAY_SIZE(features_map); j++) + { + if (i == features_map[j].bit) + { + DPRINTF(E_DBG, L_RAOP, "Speaker '%s' announced feature %d: '%s'\n", name, i, features_map[j].name); + keyval_add(features_kv, features_map[j].name, "1"); + break; + } + } + + if (j == ARRAY_SIZE(features_map)) + DPRINTF(E_DBG, L_RAOP, "Speaker '%s' announced feature %d: 'Unknown'\n", name, i); + } + + return 0; +} + +/* Examples of txt content: + * Airport Express 2: + ["pk=7de...39" "gcgl=0" "gid=0fd...4" "pi=0fd...a4" "srcvers=366.0" "protovers=1.1" "serialNumber=C8...R" "manufacturer=Apple Inc." "model=AirPort10,115" "flags=0x4" "fv=p20.78100.3" "rsf=0x0" "features=0x445D0A00,0x1C340" "deviceid=74:1B:B2:D1:1A:B7" "acl=0"] + * Apple TV 4: + ["vv=2" "osvers=14.2" "srcvers=525.38.42" "pk=c4e...c88" "psi=67C...DBC" "pi=b0b...da0" "protovers=1.1" "model=AppleTV5,3" "gcgl=1" "igl=1" "gid=B...73" "flags=0x244" "features=0x5A7FDFD5,0x3C155FDE" "fex=1d9/Wt5fFTw" "deviceid=AA:BB:CC:DD:EE:FF" "btaddr=D0:00:44:66:BB:66" "acl=0"] + * Roku + ["pk=xxxxxxxxx” "gcgl=0" "gid=xxxxxxx” "psi=xxxxx” "pi=8A:71:CA:EF:xxxx" "srcvers=377.28.01" "protovers=1.1" "serialNumber=xxxxxxx” "manufacturer=Roku" "model=3810X" "flags=0x644" "at=0x3" "fv=p20.9.40.4190" "rsf=0x3" "features=0x7F8AD0,0x10BCF46" "deviceid=8A:71:CA:xxxxx” "acl=0"] + * Samsung TV + ["pk=7xxxxxxxxxx” "gcgl=0" "gid=xxxxxxxxxxx” "psi=xxxxxxx” "pi=4C:6F:64:xxxxxxx” "srcvers=377.17.24.6" "protovers=1.1" "serialNumber=xxxxxxx” "manufacturer=Samsung" "model=UNU7090" "flags=0x244" "fv=p20.0.1" "rsf=0x3" "features=0x7F8AD0,0x38BCB46" "deviceid=64:1C:AE:xxxxx” "acl=0"] + * HomePod + ["vv=2" "osvers=14.3" "srcvers=530.6" "pk=..." "psi=31...D3" "pi=fd...87" "protovers=1.1" "model=AudioAccessory1,1" "tsid=4...E" "gpn=name" "gcgl=1" "igl=1" "gid=4...E" "flags=0x1a404" "features=0x4A7FCA00,0x3C356BD0" "fex=AMp/StBrNTw" "deviceid=D4:...:C1" "btaddr=5E:...:F1" "acl=0"] + */ +static void +airplay_device_cb(const char *name, const char *type, const char *domain, const char *hostname, int family, const char *address, int port, struct keyval *txt) +{ + struct output_device *rd; + struct airplay_extra *re; + struct keyval features = { 0 }; + cfg_t *devcfg; + cfg_opt_t *cfgopt; + const char *p; + char *s; + char *ptr; + uint64_t id; + int ret; + + p = keyval_get(txt, "deviceid"); + if (!p) + { + DPRINTF(E_LOG, L_RAOP, "AirPlay device '%s' is missing a device ID\n", name); + return; + } + + // Convert AA:BB:CC:DD:EE:FF -> AABBCCDDEEFF -> uint64 id + s = calloc(1, strlen(p) + 1); + for (ptr = s; *p != '\0'; p++) + { + if (*p == ':') + continue; + + *ptr = *p; + ptr++; + } + + ret = safe_hextou64(s, &id); + free(s); + if (ret < 0) + { + DPRINTF(E_LOG, L_RAOP, "Could not extract AirPlay device ID ('%s')\n", name); + return; + } + + DPRINTF(E_DBG, L_RAOP, "Event for AirPlay device '%s' (port %d, id %" PRIx64 ")\n", name, port, id); + + devcfg = cfg_gettsec(cfg, "airplay", name); + if (devcfg && cfg_getbool(devcfg, "exclude")) + { + DPRINTF(E_LOG, L_RAOP, "Excluding AirPlay device '%s' as set in config\n", name); + return; + } + if (devcfg && cfg_getbool(devcfg, "permanent") && (port < 0)) + { + DPRINTF(E_INFO, L_RAOP, "AirPlay device '%s' disappeared, but set as permanent in config\n", name); + return; + } + + CHECK_NULL(L_RAOP, rd = calloc(1, sizeof(struct output_device))); + CHECK_NULL(L_RAOP, re = calloc(1, sizeof(struct airplay_extra))); + + rd->id = id; + rd->name = strdup(name); + rd->type = OUTPUT_TYPE_AIRPLAY; + rd->type_name = outputs_name(rd->type); + rd->extra_device_info = re; + + if (port < 0) + { + // Device stopped advertising + switch (family) + { + case AF_INET: + rd->v4_port = 1; + break; + + case AF_INET6: + rd->v6_port = 1; + break; + } + + ret = player_device_remove(rd); + if (ret < 0) + goto free_rd; + + return; + } + + // Features, see features_map[] + p = keyval_get(txt, "features"); + if (!p || !strchr(p, ',')) + { + DPRINTF(E_LOG, L_RAOP, "AirPlay device '%s' error: Missing/unexpected 'features' in TXT field\n", name); + goto free_rd; + } + + ret = features_parse(&features, p, strchr(p, ',') + 1, name); + if (ret < 0) + goto free_rd; + + if (!keyval_get(&features, "SupportsAirPlayAudio")) + { + DPRINTF(E_LOG, L_RAOP, "AirPlay device '%s' does not support audio\n", name); + goto free_rd; + } + + if (keyval_get(&features, "MetadataFeatures_0")) + re->wanted_metadata |= RAOP_MD_WANTS_ARTWORK; + if (keyval_get(&features, "MetadataFeatures_1")) + re->wanted_metadata |= RAOP_MD_WANTS_PROGRESS; + if (keyval_get(&features, "MetadataFeatures_2")) + re->wanted_metadata |= RAOP_MD_WANTS_TEXT; + if (keyval_get(&features, "Authentication_8")) + re->supports_auth_setup = 1; + + if (keyval_get(&features, "SupportsSystemPairing") || keyval_get(&features, "SupportsCoreUtilsPairingAndEncryption")) + re->supports_pairing_transient = 1; + else if (keyval_get(&features, "SupportsHKPairingAndAccessControl")) + rd->requires_auth = 1; + + keyval_clear(&features); + + // Only default audio quality supported so far + rd->quality.sample_rate = RAOP_QUALITY_SAMPLE_RATE_DEFAULT; + rd->quality.bits_per_sample = RAOP_QUALITY_BITS_PER_SAMPLE_DEFAULT; + rd->quality.channels = RAOP_QUALITY_CHANNELS_DEFAULT; + + if (!quality_is_equal(&rd->quality, &airplay_quality_default)) + DPRINTF(E_LOG, L_RAOP, "Device '%s' requested non-default audio quality (%d/%d/%d)\n", rd->name, rd->quality.sample_rate, rd->quality.bits_per_sample, rd->quality.channels); + + // Device type + re->devtype = RAOP_DEV_OTHER; + p = keyval_get(txt, "model"); + + if (!p) + re->devtype = RAOP_DEV_APEX1_80211G; // First generation AirPort Express + else if (strncmp(p, "AirPort4", strlen("AirPort4")) == 0) + re->devtype = RAOP_DEV_APEX2_80211N; // Second generation + else if (strncmp(p, "AirPort", strlen("AirPort")) == 0) + re->devtype = RAOP_DEV_APEX3_80211N; // Third generation and newer + else if (strncmp(p, "AppleTV5,3", strlen("AppleTV5,3")) == 0) + re->devtype = RAOP_DEV_APPLETV4; // Stream to ATV with tvOS 10 needs to be kept alive + else if (strncmp(p, "AppleTV", strlen("AppleTV")) == 0) + re->devtype = RAOP_DEV_APPLETV; + else if (strncmp(p, "AudioAccessory", strlen("AudioAccessory")) == 0) + re->devtype = RAOP_DEV_HOMEPOD; + else if (*p == '\0') + DPRINTF(E_LOG, L_RAOP, "AirPlay device '%s': am has no value\n", name); + + // If the user didn't set any reconnect setting we enable for Apple TV and + // HomePods due to https://github.com/ejurgensen/forked-daapd/issues/734 + cfgopt = devcfg ? cfg_getopt(devcfg, "reconnect") : NULL; + if (cfgopt && cfgopt->nvalues == 1) + rd->resurrect = cfg_opt_getnbool(cfgopt, 0); + else + rd->resurrect = (re->devtype == RAOP_DEV_APPLETV4) || (re->devtype == RAOP_DEV_HOMEPOD); + + switch (family) + { + case AF_INET: + rd->v4_address = strdup(address); + rd->v4_port = port; + DPRINTF(E_INFO, L_RAOP, "Adding AirPlay device '%s': password: %u, verification: %u, encrypt: %u, authsetup: %u, metadata: %u, type %s, address %s:%d\n", + name, rd->has_password, rd->requires_auth, re->encrypt, re->supports_auth_setup, re->wanted_metadata, airplay_devtype[re->devtype], address, port); + break; + + case AF_INET6: + rd->v6_address = strdup(address); + rd->v6_port = port; + DPRINTF(E_INFO, L_RAOP, "Adding AirPlay device '%s': password: %u, verification: %u, encrypt: %u, authsetup: %u, metadata: %u, type %s, address [%s]:%d\n", + name, rd->has_password, rd->requires_auth, re->encrypt, re->supports_auth_setup, re->wanted_metadata, airplay_devtype[re->devtype], address, port); + break; + + default: + DPRINTF(E_LOG, L_RAOP, "Error: AirPlay device '%s' has neither ipv4 og ipv6 address\n", name); + goto free_rd; + } + + ret = player_device_add(rd); + if (ret < 0) + goto free_rd; + + return; + + free_rd: + outputs_device_free(rd); + keyval_clear(&features); +} + + +/* ---------------------------- Module definitions -------------------------- */ +/* Thread: player */ + +static int +airplay_device_start_generic(struct output_device *device, int callback_id, bool only_probe) +{ + struct airplay_session *rs; + + rs = session_make(device, callback_id); + if (!rs) + return -1; + + // After pairing/device verification, send an OPTIONS request. + if (only_probe) + rs->next_seq = AIRPLAY_SEQ_PROBE; + else + rs->next_seq = AIRPLAY_SEQ_START; + + if (device->auth_key) + sequence_start(AIRPLAY_SEQ_PAIR_VERIFY, rs, NULL, "device_start"); + else if (rs->pair_type == PAIR_HOMEKIT_TRANSIENT) + sequence_start(AIRPLAY_SEQ_PAIR_TRANSIENT, rs, NULL, "device_start"); + else + sequence_start(AIRPLAY_SEQ_PIN_START, rs, NULL, "device_start"); + + return 1; +} + +static int +airplay_device_probe(struct output_device *device, int callback_id) +{ + return airplay_device_start_generic(device, callback_id, 1); +} + +static int +airplay_device_start(struct output_device *device, int callback_id) +{ + return airplay_device_start_generic(device, callback_id, 0); +} + +static int +airplay_device_stop(struct output_device *device, int callback_id) +{ + struct airplay_session *rs = device->session; + + rs->callback_id = callback_id; + + sequence_start(AIRPLAY_SEQ_STOP, rs, NULL, "device_stop"); + + return 1; +} + +static int +airplay_device_flush(struct output_device *device, int callback_id) +{ + struct airplay_session *rs = device->session; + + if (rs->state != AIRPLAY_STATE_STREAMING) + return 0; // No-op, nothing to flush + + rs->callback_id = callback_id; + + sequence_start(AIRPLAY_SEQ_FLUSH, rs, NULL, "flush"); + + return 1; +} + +static void +airplay_device_cb_set(struct output_device *device, int callback_id) +{ + struct airplay_session *rs = device->session; + + rs->callback_id = callback_id; +} + +static void +airplay_device_free_extra(struct output_device *device) +{ + struct airplay_extra *re = device->extra_device_info; + + free(re); +} + +static int +airplay_device_authorize(struct output_device *device, const char *pin, int callback_id) +{ + struct airplay_session *rs; + + // Make a session so we can communicate with the device + rs = session_make(device, callback_id); + if (!rs) + return -1; + + sequence_start(AIRPLAY_SEQ_PAIR_SETUP, rs, (void *)pin, "device_authorize"); + + return 1; +} + +static void +airplay_write(struct output_buffer *obuf) +{ + struct airplay_master_session *rms; + struct airplay_session *rs; + int i; + + for (rms = airplay_master_sessions; rms; rms = rms->next) + { + for (i = 0; obuf->data[i].buffer; i++) + { + if (!quality_is_equal(&obuf->data[i].quality, &rms->rtp_session->quality)) + continue; + + // Set rms->cur_stamp, which involves a calculation of which session + // rtptime corresponds to the pts we are given by the player. + timestamp_set(rms, obuf->pts); + + // Sends sync packets to new sessions, and if it is sync time then also to old sessions + packets_sync_send(rms); + + // TODO avoid this copy + evbuffer_add(rms->evbuf, obuf->data[i].buffer, obuf->data[i].bufsize); + rms->evbuf_samples += obuf->data[i].samples; + + // Send as many packets as we have data for (one packet requires rawbuf_size bytes) + while (evbuffer_get_length(rms->evbuf) >= rms->rawbuf_size) + { + evbuffer_remove(rms->evbuf, rms->rawbuf, rms->rawbuf_size); + rms->evbuf_samples -= rms->samples_per_packet; + + packets_send(rms); + } + } + } + + // Check for devices that have joined since last write (we have already sent them + // initialization sync and rtp packets via packets_sync_send and packets_send) + for (rs = airplay_sessions; rs; rs = rs->next) + { + if (rs->state != AIRPLAY_STATE_CONNECTED) + continue; + + // Start sending progress to keep ATV's alive + if (!event_pending(keep_alive_timer, EV_TIMEOUT, NULL)) + evtimer_add(keep_alive_timer, &keep_alive_tv); + + rs->state = AIRPLAY_STATE_STREAMING; + // Make a cb? + } +} + +static int +airplay_init(void) +{ + int v6enabled; + int family; + int ret; + int i; + + timing_4svc.fd = -1; + timing_4svc.port = 0; + + timing_6svc.fd = -1; + timing_6svc.port = 0; + + control_4svc.fd = -1; + control_4svc.port = 0; + + control_6svc.fd = -1; + control_6svc.port = 0; + + // Check alignment of enum seq_type with airplay_seq_definition and + // airplay_seq_request + for (i = 0; i < ARRAY_SIZE(airplay_seq_definition); i++) + { + if (airplay_seq_definition[i].seq_type != i || airplay_seq_request[i][0].seq_type != i) + { + DPRINTF(E_LOG, L_RAOP, "Bug! Misalignment between sequence enum and structs: %d, %d, %d\n", i, airplay_seq_definition[i].seq_type, airplay_seq_request[i][0].seq_type); + return -1; + } + } + + CHECK_NULL(L_RAOP, keep_alive_timer = evtimer_new(evbase_player, airplay_keep_alive_timer_cb, NULL)); + + v6enabled = cfg_getbool(cfg_getsec(cfg, "general"), "ipv6"); + + ret = airplay_timing_start(v6enabled); + if (ret < 0) + { + DPRINTF(E_LOG, L_RAOP, "AirPlay time synchronization failed to start\n"); + + goto out_free_timer; + } + + ret = airplay_control_start(v6enabled); + if (ret < 0) + { + DPRINTF(E_LOG, L_RAOP, "AirPlay playback control failed to start\n"); + + goto out_stop_timing; + } + + if (v6enabled) + v6enabled = !((timing_6svc.fd < 0) || (control_6svc.fd < 0)); + + if (v6enabled) + family = AF_UNSPEC; + else + family = AF_INET; + + ret = mdns_browse("_airplay._tcp", family, airplay_device_cb, MDNS_CONNECTION_TEST); + if (ret < 0) + { + DPRINTF(E_LOG, L_RAOP, "Could not add mDNS browser for AirPlay devices\n"); + + goto out_stop_control; + } + + return 0; + + out_stop_control: + airplay_control_stop(); + out_stop_timing: + airplay_timing_stop(); + out_free_timer: + event_free(keep_alive_timer); + + return -1; +} + +static void +airplay_deinit(void) +{ + struct airplay_session *rs; + + for (rs = airplay_sessions; airplay_sessions; rs = airplay_sessions) + { + airplay_sessions = rs->next; + + session_free(rs); + } + + airplay_control_stop(); + airplay_timing_stop(); + + event_free(keep_alive_timer); +} + +struct output_definition output_airplay = +{ + .name = "AirPlay 2", + .type = OUTPUT_TYPE_AIRPLAY, + .priority = 1, + .disabled = 0, + .init = airplay_init, + .deinit = airplay_deinit, + .device_start = airplay_device_start, + .device_stop = airplay_device_stop, + .device_flush = airplay_device_flush, + .device_probe = airplay_device_probe, + .device_cb_set = airplay_device_cb_set, + .device_free_extra = airplay_device_free_extra, + .device_volume_set = airplay_set_volume_one, + .device_volume_to_pct = airplay_volume_to_pct, + .write = airplay_write, + .metadata_prepare = airplay_metadata_prepare, + .metadata_send = airplay_metadata_send, + .metadata_purge = airplay_metadata_purge, + .device_authorize = airplay_device_authorize, +}; diff --git a/src/outputs/pair-internal.h b/src/outputs/pair-internal.h new file mode 100644 index 00000000..09569f59 --- /dev/null +++ b/src/outputs/pair-internal.h @@ -0,0 +1,240 @@ +#include +#include + +struct SRPUser; + +struct pair_setup_context +{ + struct pair_definition *type; + + struct SRPUser *user; + + char pin[4]; + char device_id[17]; // Incl. zero term + + const uint8_t *pkA; + int pkA_len; + + uint8_t *pkB; + uint64_t pkB_len; + + const uint8_t *M1; + int M1_len; + + uint8_t *M2; + uint64_t M2_len; + + uint8_t *salt; + uint64_t salt_len; + uint8_t public_key[crypto_sign_PUBLICKEYBYTES]; + uint8_t private_key[crypto_sign_SECRETKEYBYTES]; + // Hex-formatet concatenation of public + private, 0-terminated + char auth_key[2 * (crypto_sign_PUBLICKEYBYTES + crypto_sign_SECRETKEYBYTES) + 1]; + + // We don't actually use the server's epk and authtag for anything + uint8_t *epk; + uint64_t epk_len; + uint8_t *authtag; + uint64_t authtag_len; + + int setup_is_completed; + const char *errmsg; +}; + +struct pair_verify_context +{ + struct pair_definition *type; + + char device_id[17]; // Incl. zero term + + uint8_t server_eph_public_key[32]; + uint8_t server_public_key[64]; + + uint8_t client_public_key[crypto_sign_PUBLICKEYBYTES]; + uint8_t client_private_key[crypto_sign_SECRETKEYBYTES]; + + uint8_t client_eph_public_key[32]; + uint8_t client_eph_private_key[32]; + + uint8_t shared_secret[32]; + + int verify_is_completed; + const char *errmsg; +}; + +struct pair_cipher_context +{ + struct pair_definition *type; + + uint8_t encryption_key[32]; + uint8_t decryption_key[32]; + + uint64_t encryption_counter; + uint64_t decryption_counter; + + const char *errmsg; +}; + +struct pair_definition +{ + struct pair_setup_context *(*pair_setup_new)(struct pair_definition *type, const char *pin, const char *device_id); + void (*pair_setup_free)(struct pair_setup_context *sctx); + int (*pair_setup_result)(const uint8_t **key, size_t *key_len, struct pair_setup_context *sctx); + + uint8_t *(*pair_setup_request1)(uint32_t *len, struct pair_setup_context *sctx); + uint8_t *(*pair_setup_request2)(uint32_t *len, struct pair_setup_context *sctx); + uint8_t *(*pair_setup_request3)(uint32_t *len, struct pair_setup_context *sctx); + + int (*pair_setup_response1)(struct pair_setup_context *sctx, const uint8_t *data, uint32_t data_len); + int (*pair_setup_response2)(struct pair_setup_context *sctx, const uint8_t *data, uint32_t data_len); + int (*pair_setup_response3)(struct pair_setup_context *sctx, const uint8_t *data, uint32_t data_len); + + uint8_t *(*pair_verify_request1)(uint32_t *len, struct pair_verify_context *vctx); + uint8_t *(*pair_verify_request2)(uint32_t *len, struct pair_verify_context *vctx); + + int (*pair_verify_response1)(struct pair_verify_context *vctx, const uint8_t *data, uint32_t data_len); + int (*pair_verify_response2)(struct pair_verify_context *vctx, const uint8_t *data, uint32_t data_len); + + struct pair_cipher_context *(*pair_cipher_new)(struct pair_definition *type, int channel, const uint8_t *shared_secret, size_t shared_secret_len); + void (*pair_cipher_free)(struct pair_cipher_context *cctx); + + int (*pair_encrypt)(uint8_t **ciphertext, size_t *ciphertext_len, uint8_t *plaintext, size_t plaintext_len, struct pair_cipher_context *cctx); + int (*pair_decrypt)(uint8_t **plaintext, size_t *plaintext_len, uint8_t *ciphertext, size_t ciphertext_len, struct pair_cipher_context *cctx); +}; + + +/* -------------------- GCRYPT AND OPENSSL COMPABILITY --------------------- */ +/* partly borrowed from ffmpeg (rtmpdh.c) */ + +#if CONFIG_GCRYPT +#include +#define SHA512_DIGEST_LENGTH 64 +#define bnum_new(bn) \ + do { \ + if (!gcry_control(GCRYCTL_INITIALIZATION_FINISHED_P)) { \ + if (!gcry_check_version("1.5.4")) \ + abort(); \ + gcry_control(GCRYCTL_DISABLE_SECMEM, 0); \ + gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0); \ + } \ + bn = gcry_mpi_new(1); \ + } while (0) +#define bnum_free(bn) gcry_mpi_release(bn) +#define bnum_num_bytes(bn) (gcry_mpi_get_nbits(bn) + 7) / 8 +#define bnum_is_zero(bn) (gcry_mpi_cmp_ui(bn, (unsigned long)0) == 0) +#define bnum_bn2bin(bn, buf, len) gcry_mpi_print(GCRYMPI_FMT_USG, buf, len, NULL, bn) +#define bnum_bin2bn(bn, buf, len) gcry_mpi_scan(&bn, GCRYMPI_FMT_USG, buf, len, NULL) +#define bnum_hex2bn(bn, buf) gcry_mpi_scan(&bn, GCRYMPI_FMT_HEX, buf, 0, 0) +#define bnum_random(bn, num_bits) gcry_mpi_randomize(bn, num_bits, GCRY_WEAK_RANDOM) +#define bnum_add(bn, a, b) gcry_mpi_add(bn, a, b) +#define bnum_sub(bn, a, b) gcry_mpi_sub(bn, a, b) +#define bnum_mul(bn, a, b) gcry_mpi_mul(bn, a, b) +typedef gcry_mpi_t bnum; +__attribute__((unused)) static void bnum_modexp(bnum bn, bnum y, bnum q, bnum p) +{ + gcry_mpi_powm(bn, y, q, p); +} +#elif CONFIG_OPENSSL +#include +#include +#include +#include +#include +#define bnum_new(bn) bn = BN_new() +#define bnum_free(bn) BN_free(bn) +#define bnum_num_bytes(bn) BN_num_bytes(bn) +#define bnum_is_zero(bn) BN_is_zero(bn) +#define bnum_bn2bin(bn, buf, len) BN_bn2bin(bn, buf) +#define bnum_bin2bn(bn, buf, len) bn = BN_bin2bn(buf, len, 0) +#define bnum_hex2bn(bn, buf) BN_hex2bn(&bn, buf) +#define bnum_random(bn, num_bits) BN_rand(bn, num_bits, 0, 0) +#define bnum_add(bn, a, b) BN_add(bn, a, b) +#define bnum_sub(bn, a, b) BN_sub(bn, a, b) +typedef BIGNUM* bnum; +__attribute__((unused)) static void bnum_mul(bnum bn, bnum a, bnum b) +{ + // No error handling + BN_CTX *ctx = BN_CTX_new(); + BN_mul(bn, a, b, ctx); + BN_CTX_free(ctx); +} +__attribute__((unused)) static void bnum_modexp(bnum bn, bnum y, bnum q, bnum p) +{ + // No error handling + BN_CTX *ctx = BN_CTX_new(); + BN_mod_exp(bn, y, q, p, ctx); + BN_CTX_free(ctx); +} +#endif + + +/* -------------------------- SHARED HASHING HELPERS ------------------------ */ + +#ifdef CONFIG_OPENSSL +enum hash_alg +{ + HASH_SHA1, + HASH_SHA224, + HASH_SHA256, + HASH_SHA384, + HASH_SHA512, +}; +#elif CONFIG_GCRYPT +enum hash_alg +{ + HASH_SHA1 = GCRY_MD_SHA1, + HASH_SHA224 = GCRY_MD_SHA224, + HASH_SHA256 = GCRY_MD_SHA256, + HASH_SHA384 = GCRY_MD_SHA384, + HASH_SHA512 = GCRY_MD_SHA512, +}; +#endif + +#if CONFIG_OPENSSL +typedef union +{ + SHA_CTX sha; + SHA256_CTX sha256; + SHA512_CTX sha512; +} HashCTX; +#elif CONFIG_GCRYPT +typedef gcry_md_hd_t HashCTX; +#endif + +int +hash_init(enum hash_alg alg, HashCTX *c); + +int +hash_update(enum hash_alg alg, HashCTX *c, const void *data, size_t len); + +int +hash_final(enum hash_alg alg, HashCTX *c, unsigned char *md); + +unsigned char * +hash(enum hash_alg alg, const unsigned char *d, size_t n, unsigned char *md); + +int +hash_length(enum hash_alg alg); + +int +hash_ab(enum hash_alg alg, unsigned char *md, const unsigned char *m1, int m1_len, const unsigned char *m2, int m2_len); + +bnum +H_nn_pad(enum hash_alg alg, const bnum n1, const bnum n2); + +bnum +H_ns(enum hash_alg alg, const bnum n, const unsigned char *bytes, int len_bytes); + +void +update_hash_n(enum hash_alg alg, HashCTX *ctx, const bnum n); + +void +hash_num(enum hash_alg alg, const bnum n, unsigned char *dest); + + +/* ----------------------------- OTHER HELPERS -------------------------------*/ + +#ifdef DEBUG_PAIR +void +hexdump(const char *msg, uint8_t *mem, size_t len); +#endif diff --git a/src/outputs/pair.c b/src/outputs/pair.c new file mode 100644 index 00000000..6b6e79ec --- /dev/null +++ b/src/outputs/pair.c @@ -0,0 +1,578 @@ +/* + * The MIT License (MIT) + * + * 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. + * + */ + +#include +#include +#include +#include // for isprint() + +#include + +#include "pair.h" +#include "pair-internal.h" + +extern struct pair_definition pair_fruit; +extern struct pair_definition pair_homekit_normal; +extern struct pair_definition pair_homekit_transient; + +// Must be in sync with enum pair_type +static struct pair_definition *pair[] = { + &pair_fruit, + &pair_homekit_normal, + &pair_homekit_transient, +}; + +/* -------------------------- SHARED HASHING HELPERS ------------------------ */ + +int +hash_init(enum hash_alg alg, HashCTX *c) +{ +#if CONFIG_OPENSSL + switch (alg) + { + case HASH_SHA1 : return SHA1_Init(&c->sha); + case HASH_SHA224: return SHA224_Init(&c->sha256); + case HASH_SHA256: return SHA256_Init(&c->sha256); + case HASH_SHA384: return SHA384_Init(&c->sha512); + case HASH_SHA512: return SHA512_Init(&c->sha512); + default: + return -1; + }; +#elif CONFIG_GCRYPT + gcry_error_t err; + + err = gcry_md_open(c, alg, 0); + + if (err) + return -1; + + return 0; +#endif +} + +int +hash_update(enum hash_alg alg, HashCTX *c, const void *data, size_t len) +{ +#if CONFIG_OPENSSL + switch (alg) + { + case HASH_SHA1 : return SHA1_Update(&c->sha, data, len); + case HASH_SHA224: return SHA224_Update(&c->sha256, data, len); + case HASH_SHA256: return SHA256_Update(&c->sha256, data, len); + case HASH_SHA384: return SHA384_Update(&c->sha512, data, len); + case HASH_SHA512: return SHA512_Update(&c->sha512, data, len); + default: + return -1; + }; +#elif CONFIG_GCRYPT + gcry_md_write(*c, data, len); + return 0; +#endif +} + +int +hash_final(enum hash_alg alg, HashCTX *c, unsigned char *md) +{ +#if CONFIG_OPENSSL + switch (alg) + { + case HASH_SHA1 : return SHA1_Final(md, &c->sha); + case HASH_SHA224: return SHA224_Final(md, &c->sha256); + case HASH_SHA256: return SHA256_Final(md, &c->sha256); + case HASH_SHA384: return SHA384_Final(md, &c->sha512); + case HASH_SHA512: return SHA512_Final(md, &c->sha512); + default: + return -1; + }; +#elif CONFIG_GCRYPT + unsigned char *buf = gcry_md_read(*c, alg); + if (!buf) + return -1; + + memcpy(md, buf, gcry_md_get_algo_dlen(alg)); + gcry_md_close(*c); + return 0; +#endif +} + +unsigned char * +hash(enum hash_alg alg, const unsigned char *d, size_t n, unsigned char *md) +{ +#if CONFIG_OPENSSL + switch (alg) + { + case HASH_SHA1 : return SHA1(d, n, md); + case HASH_SHA224: return SHA224(d, n, md); + case HASH_SHA256: return SHA256(d, n, md); + case HASH_SHA384: return SHA384(d, n, md); + case HASH_SHA512: return SHA512(d, n, md); + default: + return NULL; + }; +#elif CONFIG_GCRYPT + gcry_md_hash_buffer(alg, md, d, n); + return md; +#endif +} + +int +hash_length(enum hash_alg alg) +{ +#if CONFIG_OPENSSL + switch (alg) + { + case HASH_SHA1 : return SHA_DIGEST_LENGTH; + case HASH_SHA224: return SHA224_DIGEST_LENGTH; + case HASH_SHA256: return SHA256_DIGEST_LENGTH; + case HASH_SHA384: return SHA384_DIGEST_LENGTH; + case HASH_SHA512: return SHA512_DIGEST_LENGTH; + default: + return -1; + }; +#elif CONFIG_GCRYPT + return gcry_md_get_algo_dlen(alg); +#endif +} + +int +hash_ab(enum hash_alg alg, unsigned char *md, const unsigned char *m1, int m1_len, const unsigned char *m2, int m2_len) +{ + HashCTX ctx; + + hash_init(alg, &ctx); + hash_update(alg, &ctx, m1, m1_len); + hash_update(alg, &ctx, m2, m2_len); + return hash_final(alg, &ctx, md); +} + +bnum +H_nn_pad(enum hash_alg alg, const bnum n1, const bnum n2) +{ + bnum bn; + unsigned char *bin; + unsigned char buff[SHA512_DIGEST_LENGTH]; + int len_n1 = bnum_num_bytes(n1); + int len_n2 = bnum_num_bytes(n2); + int nbytes = 2 * len_n1; + + if ((len_n2 < 1) || (len_n2 > len_n1)) + return 0; + + bin = calloc( 1, nbytes ); + + bnum_bn2bin(n1, bin, len_n1); + bnum_bn2bin(n2, bin + nbytes - len_n2, len_n2); + hash( alg, bin, nbytes, buff ); + free(bin); + bnum_bin2bn(bn, buff, hash_length(alg)); + return bn; +} + +bnum +H_ns(enum hash_alg alg, const bnum n, const unsigned char *bytes, int len_bytes) +{ + bnum bn; + unsigned char buff[SHA512_DIGEST_LENGTH]; + int len_n = bnum_num_bytes(n); + int nbytes = len_n + len_bytes; + unsigned char *bin = malloc(nbytes); + + bnum_bn2bin(n, bin, len_n); + memcpy( bin + len_n, bytes, len_bytes ); + hash( alg, bin, nbytes, buff ); + free(bin); + bnum_bin2bn(bn, buff, hash_length(alg)); + return bn; +} + +void +update_hash_n(enum hash_alg alg, HashCTX *ctx, const bnum n) +{ + unsigned long len = bnum_num_bytes(n); + unsigned char *n_bytes = malloc(len); + + bnum_bn2bin(n, n_bytes, len); + hash_update(alg, ctx, n_bytes, len); + free(n_bytes); +} + +void +hash_num(enum hash_alg alg, const bnum n, unsigned char *dest) +{ + int nbytes = bnum_num_bytes(n); + unsigned char *bin = malloc(nbytes); + + bnum_bn2bin(n, bin, nbytes); + hash( alg, bin, nbytes, dest ); + free(bin); +} + + +/* ----------------------------- OTHER HELPERS -------------------------------*/ + +#ifdef DEBUG_PAIR +void +hexdump(const char *msg, uint8_t *mem, size_t len) +{ + int i, j; + int hexdump_cols = 16; + + if (msg) + printf("%s", msg); + + for (i = 0; i < len + ((len % hexdump_cols) ? (hexdump_cols - len % hexdump_cols) : 0); i++) + { + if(i % hexdump_cols == 0) + printf("0x%06x: ", i); + + if (i < len) + printf("%02x ", 0xFF & ((char*)mem)[i]); + else + printf(" "); + + if (i % hexdump_cols == (hexdump_cols - 1)) + { + for (j = i - (hexdump_cols - 1); j <= i; j++) + { + if (j >= len) + putchar(' '); + else if (isprint(((char*)mem)[j])) + putchar(0xFF & ((char*)mem)[j]); + else + putchar('.'); + } + + putchar('\n'); + } + } +} +#endif + + +/* ----------------------------------- API -----------------------------------*/ + +struct pair_setup_context * +pair_setup_new(enum pair_type type, const char *pin, const char *device_id) +{ + if (!pair[type]->pair_setup_new) + return NULL; + + return pair[type]->pair_setup_new(pair[type], pin, device_id); +} + +void +pair_setup_free(struct pair_setup_context *sctx) +{ + if (!sctx) + return; + + if (!sctx->type->pair_setup_free) + return; + + return sctx->type->pair_setup_free(sctx); +} + +const char * +pair_setup_errmsg(struct pair_setup_context *sctx) +{ + return sctx->errmsg; +} + +uint8_t * +pair_setup_request1(uint32_t *len, struct pair_setup_context *sctx) +{ + if (!sctx->type->pair_setup_request1) + return NULL; + + return sctx->type->pair_setup_request1(len, sctx); + + if (!sctx->type->pair_setup_request1) + return NULL; + + return sctx->type->pair_setup_request1(len, sctx); +} + +uint8_t * +pair_setup_request2(uint32_t *len, struct pair_setup_context *sctx) +{ + if (!sctx->type->pair_setup_request2) + return NULL; + + return sctx->type->pair_setup_request2(len, sctx); +} + +uint8_t * +pair_setup_request3(uint32_t *len, struct pair_setup_context *sctx) +{ + if (!sctx->type->pair_setup_request3) + return NULL; + + return sctx->type->pair_setup_request3(len, sctx); +} + +int +pair_setup_response1(struct pair_setup_context *sctx, const uint8_t *data, uint32_t data_len) +{ + if (!sctx->type->pair_setup_response1) + return -1; + + return sctx->type->pair_setup_response1(sctx, data, data_len); +} + +int +pair_setup_response2(struct pair_setup_context *sctx, const uint8_t *data, uint32_t data_len) +{ + if (!sctx->type->pair_setup_response2) + return -1; + + return sctx->type->pair_setup_response2(sctx, data, data_len); +} + +int +pair_setup_response3(struct pair_setup_context *sctx, const uint8_t *data, uint32_t data_len) +{ + if (!sctx->type->pair_setup_response3) + return -1; + + if (sctx->type->pair_setup_response3(sctx, data, data_len) != 0) + return -1; + + return 0; +} + +int +pair_setup_result(const char **hexkey, const uint8_t **key, size_t *key_len, struct pair_setup_context *sctx) +{ + const uint8_t *out_key; + size_t out_len; + char *ptr; + int i; + + if (!sctx->setup_is_completed) + { + sctx->errmsg = "Setup result: The pair setup has not been completed"; + return -1; + } + + if (!sctx->type->pair_setup_result) + return -1; + + if (sctx->type->pair_setup_result(&out_key, &out_len, sctx) != 0) + return -1; + + if (2 * out_len + 1 > sizeof(sctx->auth_key)) + return -1; + + ptr = sctx->auth_key; + for (i = 0; i < out_len; i++) + ptr += sprintf(ptr, "%02x", out_key[i]); + *ptr = '\0'; + + if (key) + *key = out_key; + if (key_len) + *key_len = out_len; + if (hexkey) + *hexkey = sctx->auth_key; + + return 0; +} + + +struct pair_verify_context * +pair_verify_new(enum pair_type type, const char *hexkey, const char *device_id) +{ + struct pair_verify_context *vctx; + char hex[] = { 0, 0, 0 }; + size_t hexkey_len; + const char *ptr; + int i; + + if (sodium_init() == -1) + return NULL; + + if (!hexkey) + return NULL; + + hexkey_len = strlen(hexkey); + + if (hexkey_len != 2 * sizeof(vctx->client_private_key)) + return NULL; + + if (device_id && strlen(device_id) != 16) + return NULL; + + vctx = calloc(1, sizeof(struct pair_verify_context)); + if (!vctx) + return NULL; + + vctx->type = pair[type]; + + if (device_id) + memcpy(vctx->device_id, device_id, strlen(device_id)); + + ptr = hexkey; + for (i = 0; i < sizeof(vctx->client_private_key); i++, ptr+=2) + { + hex[0] = ptr[0]; + hex[1] = ptr[1]; + vctx->client_private_key[i] = strtol(hex, NULL, 16); + } + + ptr = hexkey + hexkey_len - 2 * sizeof(vctx->client_public_key); + for (i = 0; i < sizeof(vctx->client_public_key); i++, ptr+=2) + { + hex[0] = ptr[0]; + hex[1] = ptr[1]; + vctx->client_public_key[i] = strtol(hex, NULL, 16); + } + + return vctx; +} + +void +pair_verify_free(struct pair_verify_context *vctx) +{ + if (!vctx) + return; + + free(vctx); +} + +const char * +pair_verify_errmsg(struct pair_verify_context *vctx) +{ + return vctx->errmsg; +} + +uint8_t * +pair_verify_request1(uint32_t *len, struct pair_verify_context *vctx) +{ + if (!vctx->type->pair_verify_request1) + return NULL; + + return vctx->type->pair_verify_request1(len, vctx); +} + +uint8_t * +pair_verify_request2(uint32_t *len, struct pair_verify_context *vctx) +{ + if (!vctx->type->pair_verify_request2) + return NULL; + + return vctx->type->pair_verify_request2(len, vctx); +} + +int +pair_verify_response1(struct pair_verify_context *vctx, const uint8_t *data, uint32_t data_len) +{ + if (!vctx->type->pair_verify_response1) + return -1; + + return vctx->type->pair_verify_response1(vctx, data, data_len); +} + +int +pair_verify_response2(struct pair_verify_context *vctx, const uint8_t *data, uint32_t data_len) +{ + if (!vctx->type->pair_verify_response2) + return -1; + + if (vctx->type->pair_verify_response2(vctx, data, data_len) != 0) + return -1; + + vctx->verify_is_completed = 1; + return 0; +} + +int +pair_verify_result(const uint8_t **shared_secret, size_t *shared_secret_len, struct pair_verify_context *vctx) +{ + if (!vctx->verify_is_completed) + { + vctx->errmsg = "Verify result: The pairing verification did not complete"; + return -1; + } + + *shared_secret = vctx->shared_secret; + *shared_secret_len = sizeof(vctx->shared_secret); + + return 0; +} + +struct pair_cipher_context * +pair_cipher_new(enum pair_type type, int channel, const uint8_t *shared_secret, size_t shared_secret_len) +{ + if (!pair[type]->pair_cipher_new) + return NULL; + + return pair[type]->pair_cipher_new(pair[type], channel, shared_secret, shared_secret_len); +} + +void +pair_cipher_free(struct pair_cipher_context *cctx) +{ + if (!cctx) + return; + + if (!cctx->type->pair_cipher_free) + return; + + return cctx->type->pair_cipher_free(cctx); +} + +const char * +pair_cipher_errmsg(struct pair_cipher_context *cctx) +{ + return cctx->errmsg; +} + +int +pair_encrypt(uint8_t **ciphertext, size_t *ciphertext_len, uint8_t *plaintext, size_t plaintext_len, struct pair_cipher_context *cctx) +{ + if (!cctx->type->pair_encrypt) + return 0; + + return cctx->type->pair_encrypt(ciphertext, ciphertext_len, plaintext, plaintext_len, cctx); +} + +int +pair_decrypt(uint8_t **plaintext, size_t *plaintext_len, uint8_t *ciphertext, size_t ciphertext_len, struct pair_cipher_context *cctx) +{ + if (!cctx->type->pair_decrypt) + return 0; + + return cctx->type->pair_decrypt(plaintext, plaintext_len, ciphertext, ciphertext_len, cctx); +} + +void +pair_encrypt_rollback(struct pair_cipher_context *cctx) +{ + cctx->encryption_counter--; +} + +void +pair_decrypt_rollback(struct pair_cipher_context *cctx) +{ + cctx->decryption_counter--; +} diff --git a/src/outputs/pair.h b/src/outputs/pair.h new file mode 100644 index 00000000..87ce50aa --- /dev/null +++ b/src/outputs/pair.h @@ -0,0 +1,117 @@ +#ifndef __PAIR_AP_H__ +#define __PAIR_AP_H__ + +#include + +enum pair_type +{ + // This is the pairing type required for Apple TV device verification, which + // became mandatory with tvOS 10.2. + PAIR_FRUIT, + // This is the Homekit type required for AirPlay 2 with both PIN setup and + // verification + PAIR_HOMEKIT_NORMAL, + // Same as normal except PIN is fixed to 3939 and stops after setup step 2, + // when session key is established + PAIR_HOMEKIT_TRANSIENT, +}; + +struct pair_setup_context; +struct pair_verify_context; +struct pair_cipher_context; + +/* When you have the pin-code (must be 4 bytes), create a new context with this + * function and then call pair_setup_request1(). device_id is only + * required for homekit pairing, where it should have length 16. + */ +struct pair_setup_context * +pair_setup_new(enum pair_type type, const char *pin, const char *device_id); +void +pair_setup_free(struct pair_setup_context *sctx); + +/* Returns last error message + */ +const char * +pair_setup_errmsg(struct pair_setup_context *sctx); + +uint8_t * +pair_setup_request1(uint32_t *len, struct pair_setup_context *sctx); +uint8_t * +pair_setup_request2(uint32_t *len, struct pair_setup_context *sctx); +uint8_t * +pair_setup_request3(uint32_t *len, struct pair_setup_context *sctx); + +int +pair_setup_response1(struct pair_setup_context *sctx, const uint8_t *data, uint32_t data_len); +int +pair_setup_response2(struct pair_setup_context *sctx, const uint8_t *data, uint32_t data_len); +int +pair_setup_response3(struct pair_setup_context *sctx, const uint8_t *data, uint32_t data_len); + +/* Returns a 0-terminated string that is the authorisation key, along with a + * pointer to the binary representation. The string can be used to initialize + * pair_verify_new(). + * Note that the pointers become invalid when you free sctx. + */ +int +pair_setup_result(const char **hexkey, const uint8_t **key, size_t *key_len, struct pair_setup_context *sctx); + + +/* When you have completed the setup you can extract a key with + * pair_setup_result(). Give the string as input to this function to + * create a verification context and then call pair_verify_request1() + * device_id is only required for homekit pairing, where it should have len 16. + */ +struct pair_verify_context * +pair_verify_new(enum pair_type type, const char *hexkey, const char *device_id); +void +pair_verify_free(struct pair_verify_context *vctx); + +/* Returns last error message + */ +const char * +pair_verify_errmsg(struct pair_verify_context *vctx); + +uint8_t * +pair_verify_request1(uint32_t *len, struct pair_verify_context *vctx); +uint8_t * +pair_verify_request2(uint32_t *len, struct pair_verify_context *vctx); + +int +pair_verify_response1(struct pair_verify_context *vctx, const uint8_t *data, uint32_t data_len); +int +pair_verify_response2(struct pair_verify_context *vctx, const uint8_t *data, uint32_t data_len); + +/* Returns a pointer to the shared secret that is the result of the pairing. + * Note that the pointers become invalid when you free vctx. + */ +int +pair_verify_result(const uint8_t **shared_secret, size_t *shared_secret_len, struct pair_verify_context *vctx); + +/* When you have completed the verification you can extract a key with + * pair_verify_result(). Give the shared secret as input to this function to + * create a ciphering context. + */ +struct pair_cipher_context * +pair_cipher_new(enum pair_type type, int channel, const uint8_t *shared_secret, size_t shared_secret_len); +void +pair_cipher_free(struct pair_cipher_context *cctx); + +/* Returns last error message + */ +const char * +pair_cipher_errmsg(struct pair_cipher_context *cctx); + +int +pair_encrypt(uint8_t **ciphertext, size_t *ciphertext_len, uint8_t *plaintext, size_t plaintext_len, struct pair_cipher_context *cctx); +int +pair_decrypt(uint8_t **plaintext, size_t *plaintext_len, uint8_t *ciphertext, size_t ciphertext_len, struct pair_cipher_context *cctx); + +/* Rolls back the nonce + */ +void +pair_encrypt_rollback(struct pair_cipher_context *cctx); +void +pair_decrypt_rollback(struct pair_cipher_context *cctx); + +#endif /* !__PAIR_AP_H__ */ diff --git a/src/outputs/raop_verification.c b/src/outputs/pair_fruit.c similarity index 63% rename from src/outputs/raop_verification.c rename to src/outputs/pair_fruit.c index 24f07520..baeaf429 100644 --- a/src/outputs/raop_verification.c +++ b/src/outputs/pair_fruit.c @@ -1,22 +1,21 @@ /* * - * The Secure Remote Password 6a implementation included here is by + * The Secure Remote Password 6a implementation is adapted from: * - Tom Cocagne * * - * * The MIT License (MIT) - * + * * 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 @@ -24,7 +23,7 @@ * 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. - * + * */ #include @@ -34,74 +33,7 @@ #include #include -#include "raop_verification.h" - -#define CONFIG_GCRYPT 1 - -/* -------------------- GCRYPT AND OPENSSL COMPABILITY --------------------- */ -/* partly borrowed from ffmpeg (rtmpdh.c) */ - -#if CONFIG_GCRYPT -#include -#define SHA512_DIGEST_LENGTH 64 -#define bnum_new(bn) \ - do { \ - if (!gcry_control(GCRYCTL_INITIALIZATION_FINISHED_P)) { \ - if (!gcry_check_version("1.5.4")) \ - abort(); \ - gcry_control(GCRYCTL_DISABLE_SECMEM, 0); \ - gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0); \ - } \ - bn = gcry_mpi_new(1); \ - } while (0) -#define bnum_free(bn) gcry_mpi_release(bn) -#define bnum_num_bytes(bn) (gcry_mpi_get_nbits(bn) + 7) / 8 -#define bnum_is_zero(bn) (gcry_mpi_cmp_ui(bn, (unsigned long)0) == 0) -#define bnum_bn2bin(bn, buf, len) gcry_mpi_print(GCRYMPI_FMT_USG, buf, len, NULL, bn) -#define bnum_bin2bn(bn, buf, len) gcry_mpi_scan(&bn, GCRYMPI_FMT_USG, buf, len, NULL) -#define bnum_hex2bn(bn, buf) gcry_mpi_scan(&bn, GCRYMPI_FMT_HEX, buf, 0, 0) -#define bnum_random(bn, num_bits) gcry_mpi_randomize(bn, num_bits, GCRY_WEAK_RANDOM) -#define bnum_add(bn, a, b) gcry_mpi_add(bn, a, b) -#define bnum_sub(bn, a, b) gcry_mpi_sub(bn, a, b) -#define bnum_mul(bn, a, b) gcry_mpi_mul(bn, a, b) -typedef gcry_mpi_t bnum; -static void bnum_modexp(bnum bn, bnum y, bnum q, bnum p) -{ - gcry_mpi_powm(bn, y, q, p); -} -#elif CONFIG_OPENSSL -#include -#include -#include -#include -#include -#define bnum_new(bn) bn = BN_new() -#define bnum_free(bn) BN_free(bn) -#define bnum_num_bytes(bn) BN_num_bytes(bn) -#define bnum_is_zero(bn) BN_is_zero(bn) -#define bnum_bn2bin(bn, buf, len) BN_bn2bin(bn, buf) -#define bnum_bin2bn(bn, buf, len) bn = BN_bin2bn(buf, len, 0) -#define bnum_hex2bn(bn, buf) BN_hex2bn(&bn, buf) -#define bnum_random(bn, num_bits) BN_rand(bn, num_bits, 0, 0) -#define bnum_add(bn, a, b) BN_add(bn, a, b) -#define bnum_sub(bn, a, b) BN_sub(bn, a, b) -typedef BIGNUM* bnum; -static void bnum_mul(bnum bn, bnum a, bnum b) -{ - // No error handling - BN_CTX *ctx = BN_CTX_new(); - BN_mul(bn, a, b, ctx); - BN_CTX_free(ctx); -} -static void bnum_modexp(bnum bn, bnum y, bnum q, bnum p) -{ - // No error handling - BN_CTX *ctx = BN_CTX_new(); - BN_mod_exp(bn, y, q, p, ctx); - BN_CTX_free(ctx); -} -#endif - +#include "pair-internal.h" /* ----------------------------- DEFINES ETC ------------------------------- */ @@ -113,74 +45,6 @@ static void bnum_modexp(bnum bn, bnum y, bnum q, bnum p) #define AES_VERIFY_KEY "Pair-Verify-AES-Key" #define AES_VERIFY_IV "Pair-Verify-AES-IV" -#ifdef CONFIG_OPENSSL -enum hash_alg -{ - HASH_SHA1, - HASH_SHA224, - HASH_SHA256, - HASH_SHA384, - HASH_SHA512, -}; -#elif CONFIG_GCRYPT -enum hash_alg -{ - HASH_SHA1 = GCRY_MD_SHA1, - HASH_SHA224 = GCRY_MD_SHA224, - HASH_SHA256 = GCRY_MD_SHA256, - HASH_SHA384 = GCRY_MD_SHA384, - HASH_SHA512 = GCRY_MD_SHA512, -}; -#endif - -struct verification_setup_context -{ - struct SRPUser *user; - - char pin[4]; - - const uint8_t *pkA; - int pkA_len; - - uint8_t *pkB; - uint64_t pkB_len; - - const uint8_t *M1; - int M1_len; - - uint8_t *M2; - uint64_t M2_len; - - uint8_t *salt; - uint64_t salt_len; - uint8_t public_key[crypto_sign_PUBLICKEYBYTES]; - uint8_t private_key[crypto_sign_SECRETKEYBYTES]; - // Hex-formatet concatenation of public + private, 0-terminated - char auth_key[2 * (crypto_sign_PUBLICKEYBYTES + crypto_sign_SECRETKEYBYTES) + 1]; - - // We don't actually use the server's epk and authtag for anything - uint8_t *epk; - uint64_t epk_len; - uint8_t *authtag; - uint64_t authtag_len; - - const char *errmsg; -}; - -struct verification_verify_context -{ - uint8_t server_eph_public_key[32]; - uint8_t server_public_key[64]; - - uint8_t client_public_key[crypto_sign_PUBLICKEYBYTES]; - uint8_t client_private_key[crypto_sign_SECRETKEYBYTES]; - - uint8_t client_eph_public_key[32]; - uint8_t client_eph_private_key[32]; - - const char *errmsg; -}; - /* ---------------------------------- SRP ---------------------------------- */ @@ -196,40 +60,29 @@ typedef struct bnum g; } NGConstant; -#if CONFIG_OPENSSL -typedef union -{ - SHA_CTX sha; - SHA256_CTX sha256; - SHA512_CTX sha512; -} HashCTX; -#elif CONFIG_GCRYPT -typedef gcry_md_hd_t HashCTX; -#endif - struct SRPUser { enum hash_alg alg; NGConstant *ng; - + bnum a; bnum A; bnum S; const unsigned char *bytes_A; - int authenticated; - - const char *username; - const unsigned char *password; - int password_len; - + int authenticated; + + char *username; + unsigned char *password; + int password_len; + unsigned char M [SHA512_DIGEST_LENGTH]; unsigned char H_AMK [SHA512_DIGEST_LENGTH]; unsigned char session_key [2 * SHA512_DIGEST_LENGTH]; // See hash_session_key() int session_key_len; }; -struct NGHex +struct NGHex { const char *n_hex; const char *g_hex; @@ -263,10 +116,10 @@ new_ng(SRP_NGType ng_type, const char *n_hex, const char *g_hex) n_hex = global_Ng_constants[ ng_type ].n_hex; g_hex = global_Ng_constants[ ng_type ].g_hex; } - + bnum_hex2bn(ng->N, n_hex); bnum_hex2bn(ng->g, g_hex); - + return ng; } @@ -281,167 +134,6 @@ free_ng(NGConstant * ng) free(ng); } -static int -hash_init(enum hash_alg alg, HashCTX *c) -{ -#if CONFIG_OPENSSL - switch (alg) - { - case HASH_SHA1 : return SHA1_Init(&c->sha); - case HASH_SHA224: return SHA224_Init(&c->sha256); - case HASH_SHA256: return SHA256_Init(&c->sha256); - case HASH_SHA384: return SHA384_Init(&c->sha512); - case HASH_SHA512: return SHA512_Init(&c->sha512); - default: - return -1; - }; -#elif CONFIG_GCRYPT - gcry_error_t err; - - err = gcry_md_open(c, alg, 0); - - if (err) - return -1; - - return 0; -#endif -} - -static int -hash_update(enum hash_alg alg, HashCTX *c, const void *data, size_t len) -{ -#if CONFIG_OPENSSL - switch (alg) - { - case HASH_SHA1 : return SHA1_Update(&c->sha, data, len); - case HASH_SHA224: return SHA224_Update(&c->sha256, data, len); - case HASH_SHA256: return SHA256_Update(&c->sha256, data, len); - case HASH_SHA384: return SHA384_Update(&c->sha512, data, len); - case HASH_SHA512: return SHA512_Update(&c->sha512, data, len); - default: - return -1; - }; -#elif CONFIG_GCRYPT - gcry_md_write(*c, data, len); - return 0; -#endif -} - -static int -hash_final(enum hash_alg alg, HashCTX *c, unsigned char *md) -{ -#if CONFIG_OPENSSL - switch (alg) - { - case HASH_SHA1 : return SHA1_Final(md, &c->sha); - case HASH_SHA224: return SHA224_Final(md, &c->sha256); - case HASH_SHA256: return SHA256_Final(md, &c->sha256); - case HASH_SHA384: return SHA384_Final(md, &c->sha512); - case HASH_SHA512: return SHA512_Final(md, &c->sha512); - default: - return -1; - }; -#elif CONFIG_GCRYPT - unsigned char *buf = gcry_md_read(*c, alg); - if (!buf) - return -1; - - memcpy(md, buf, gcry_md_get_algo_dlen(alg)); - gcry_md_close(*c); - return 0; -#endif -} - -static unsigned char * -hash(enum hash_alg alg, const unsigned char *d, size_t n, unsigned char *md) -{ -#if CONFIG_OPENSSL - switch (alg) - { - case HASH_SHA1 : return SHA1(d, n, md); - case HASH_SHA224: return SHA224(d, n, md); - case HASH_SHA256: return SHA256(d, n, md); - case HASH_SHA384: return SHA384(d, n, md); - case HASH_SHA512: return SHA512(d, n, md); - default: - return NULL; - }; -#elif CONFIG_GCRYPT - gcry_md_hash_buffer(alg, md, d, n); - return md; -#endif -} - -static int -hash_length(enum hash_alg alg) -{ -#if CONFIG_OPENSSL - switch (alg) - { - case HASH_SHA1 : return SHA_DIGEST_LENGTH; - case HASH_SHA224: return SHA224_DIGEST_LENGTH; - case HASH_SHA256: return SHA256_DIGEST_LENGTH; - case HASH_SHA384: return SHA384_DIGEST_LENGTH; - case HASH_SHA512: return SHA512_DIGEST_LENGTH; - default: - return -1; - }; -#elif CONFIG_GCRYPT - return gcry_md_get_algo_dlen(alg); -#endif -} - -static int -hash_ab(enum hash_alg alg, unsigned char *md, const unsigned char *m1, int m1_len, const unsigned char *m2, int m2_len) -{ - HashCTX ctx; - - hash_init(alg, &ctx); - hash_update(alg, &ctx, m1, m1_len); - hash_update(alg, &ctx, m2, m2_len); - return hash_final(alg, &ctx, md); -} - -static bnum -H_nn_pad(enum hash_alg alg, const bnum n1, const bnum n2) -{ - bnum bn; - unsigned char *bin; - unsigned char buff[SHA512_DIGEST_LENGTH]; - int len_n1 = bnum_num_bytes(n1); - int len_n2 = bnum_num_bytes(n2); - int nbytes = 2 * len_n1; - - if ((len_n2 < 1) || (len_n2 > len_n1)) - return 0; - - bin = calloc( 1, nbytes ); - - bnum_bn2bin(n1, bin, len_n1); - bnum_bn2bin(n2, bin + nbytes - len_n2, len_n2); - hash( alg, bin, nbytes, buff ); - free(bin); - bnum_bin2bn(bn, buff, hash_length(alg)); - return bn; -} - -static bnum -H_ns(enum hash_alg alg, const bnum n, const unsigned char *bytes, int len_bytes) -{ - bnum bn; - unsigned char buff[SHA512_DIGEST_LENGTH]; - int len_n = bnum_num_bytes(n); - int nbytes = len_n + len_bytes; - unsigned char *bin = malloc(nbytes); - - bnum_bn2bin(n, bin, len_n); - memcpy( bin + len_n, bytes, len_bytes ); - hash( alg, bin, nbytes, buff ); - free(bin); - bnum_bin2bn(bn, buff, hash_length(alg)); - return bn; -} - static bnum calculate_x(enum hash_alg alg, const bnum salt, const char *username, const unsigned char *password, int password_len) { @@ -453,30 +145,10 @@ calculate_x(enum hash_alg alg, const bnum salt, const char *username, const unsi hash_update( alg, &ctx, ":", 1 ); hash_update( alg, &ctx, password, password_len ); hash_final( alg, &ctx, ucp_hash ); - + return H_ns( alg, salt, ucp_hash, hash_length(alg) ); } -static void -update_hash_n(enum hash_alg alg, HashCTX *ctx, const bnum n) -{ - unsigned long len = bnum_num_bytes(n); - unsigned char *n_bytes = malloc(len); - bnum_bn2bin(n, n_bytes, len); - hash_update(alg, ctx, n_bytes, len); - free(n_bytes); -} - -static void -hash_num(enum hash_alg alg, const bnum n, unsigned char *dest) -{ - int nbytes = bnum_num_bytes(n); - unsigned char *bin = malloc(nbytes); - bnum_bn2bin(n, bin, nbytes); - hash( alg, bin, nbytes, dest ); - free(bin); -} - static int hash_session_key(enum hash_alg alg, const bnum n, unsigned char *dest) { @@ -508,24 +180,24 @@ calculate_M(enum hash_alg alg, NGConstant *ng, unsigned char *dest, const char * HashCTX ctx; int i = 0; int hash_len = hash_length(alg); - + hash_num( alg, ng->N, H_N ); hash_num( alg, ng->g, H_g ); - + hash(alg, (const unsigned char *)I, strlen(I), H_I); - - for (i = 0; i < hash_len; i++) + + for (i=0; i < hash_len; i++ ) H_xor[i] = H_N[i] ^ H_g[i]; hash_init( alg, &ctx ); - + hash_update( alg, &ctx, H_xor, hash_len ); hash_update( alg, &ctx, H_I, hash_len ); update_hash_n( alg, &ctx, s ); update_hash_n( alg, &ctx, A ); update_hash_n( alg, &ctx, B ); hash_update( alg, &ctx, K, K_len ); - + hash_final( alg, &ctx, dest ); } @@ -533,18 +205,18 @@ static void calculate_H_AMK(enum hash_alg alg, unsigned char *dest, const bnum A, const unsigned char * M, const unsigned char * K, int K_len) { HashCTX ctx; - + hash_init( alg, &ctx ); - + update_hash_n( alg, &ctx, A ); hash_update( alg, &ctx, M, hash_length(alg) ); hash_update( alg, &ctx, K, K_len ); - + hash_final( alg, &ctx, dest ); } static struct SRPUser * -srp_user_new(enum hash_alg alg, SRP_NGType ng_type, const char *username, +srp_user_new(enum hash_alg alg, SRP_NGType ng_type, const char *username, const unsigned char *bytes_password, int len_password, const char *n_hex, const char *g_hex) { @@ -556,27 +228,27 @@ srp_user_new(enum hash_alg alg, SRP_NGType ng_type, const char *username, usr->alg = alg; usr->ng = new_ng( ng_type, n_hex, g_hex ); - + bnum_new(usr->a); bnum_new(usr->A); bnum_new(usr->S); if (!usr->ng || !usr->a || !usr->A || !usr->S) goto err_exit; - - usr->username = (const char *) malloc(ulen); - usr->password = (const unsigned char *) malloc(len_password); + + usr->username = malloc(ulen); + usr->password = malloc(len_password); usr->password_len = len_password; if (!usr->username || !usr->password) goto err_exit; - - memcpy((char *)usr->username, username, ulen); - memcpy((char *)usr->password, bytes_password, len_password); + + memcpy(usr->username, username, ulen); + memcpy(usr->password, bytes_password, len_password); usr->authenticated = 0; usr->bytes_A = 0; - + return usr; err_exit: @@ -586,12 +258,12 @@ srp_user_new(enum hash_alg alg, SRP_NGType ng_type, const char *username, bnum_free(usr->a); bnum_free(usr->A); bnum_free(usr->S); - if (usr->username) - free((void*)usr->username); + + free(usr->username); if (usr->password) { - memset((void*)usr->password, 0, usr->password_len); - free((void*)usr->password); + memset(usr->password, 0, usr->password_len); + free(usr->password); } free(usr); @@ -599,7 +271,7 @@ srp_user_new(enum hash_alg alg, SRP_NGType ng_type, const char *username, } static void -srp_user_delete(struct SRPUser *usr) +srp_user_free(struct SRPUser *usr) { if(!usr) return; @@ -607,16 +279,14 @@ srp_user_delete(struct SRPUser *usr) bnum_free(usr->a); bnum_free(usr->A); bnum_free(usr->S); - + free_ng(usr->ng); - memset((void*)usr->password, 0, usr->password_len); - - free((char *)usr->username); - free((char *)usr->password); - - if (usr->bytes_A) - free( (char *)usr->bytes_A ); + memset(usr->password, 0, usr->password_len); + + free(usr->username); + free(usr->password); + free((char *)usr->bytes_A); memset(usr, 0, sizeof(*usr)); free(usr); @@ -643,7 +313,7 @@ srp_user_start_authentication(struct SRPUser *usr, const char **username, { bnum_random(usr->a, 256); bnum_modexp(usr->A, usr->ng->g, usr->a, usr->ng->N); - + *len_A = bnum_num_bytes(usr->A); *bytes_A = malloc(*len_A); @@ -654,9 +324,9 @@ srp_user_start_authentication(struct SRPUser *usr, const char **username, *username = 0; return; } - + bnum_bn2bin(usr->A, (unsigned char *) *bytes_A, *len_A); - + usr->bytes_A = *bytes_A; *username = usr->username; } @@ -694,7 +364,7 @@ srp_user_process_challenge(struct SRPUser *usr, const unsigned char *bytes_s, in if (!bnum_is_zero(B) && !bnum_is_zero(u)) { bnum_modexp(v, usr->ng->g, x, usr->ng->N); - + // S = (B - k*(g^x)) ^ (a + ux) bnum_mul(tmp1, u, x); bnum_add(tmp2, usr->a, tmp1); // tmp2 = (a + ux) @@ -704,17 +374,19 @@ srp_user_process_challenge(struct SRPUser *usr, const unsigned char *bytes_s, in bnum_modexp(usr->S, tmp1, tmp2, usr->ng->N); usr->session_key_len = hash_session_key(usr->alg, usr->S, usr->session_key); - + calculate_M(usr->alg, usr->ng, usr->M, usr->username, s, usr->A, B, usr->session_key, usr->session_key_len); calculate_H_AMK(usr->alg, usr->H_AMK, usr->A, usr->M, usr->session_key, usr->session_key_len); - + *bytes_M = usr->M; - *len_M = hash_length(usr->alg); + if (len_M) + *len_M = hash_length(usr->alg); } else { *bytes_M = NULL; - *len_M = 0; + if (len_M) + *len_M = 0; } cleanup2: @@ -920,32 +592,37 @@ encrypt_ctr(unsigned char *ciphertext, int ciphertext_len, } -/* ---------------------------------- API ---------------------------------- */ +/* -------------------------- IMPLEMENTATION -------------------------------- */ -struct verification_setup_context * -verification_setup_new(const char *pin) +static struct pair_setup_context * +pair_setup_new(struct pair_definition *type, const char *pin, const char *device_id) { - struct verification_setup_context *sctx; + struct pair_setup_context *sctx; if (sodium_init() == -1) return NULL; - sctx = calloc(1, sizeof(struct verification_setup_context)); + if (!pin || strlen(pin) < 4) + return NULL; + + sctx = calloc(1, sizeof(struct pair_setup_context)); if (!sctx) return NULL; + sctx->type = type; + memcpy(sctx->pin, pin, sizeof(sctx->pin)); return sctx; } -void -verification_setup_free(struct verification_setup_context *sctx) +static void +pair_setup_free(struct pair_setup_context *sctx) { if (!sctx) return; - srp_user_delete(sctx->user); + srp_user_free(sctx->user); free(sctx->pkB); free(sctx->M2); @@ -956,14 +633,8 @@ verification_setup_free(struct verification_setup_context *sctx) free(sctx); } -const char * -verification_setup_errmsg(struct verification_setup_context *sctx) -{ - return sctx->errmsg; -} - -uint8_t * -verification_setup_request1(uint32_t *len, struct verification_setup_context *sctx) +static uint8_t * +pair_setup_request1(uint32_t *len, struct pair_setup_context *sctx) { plist_t dict; plist_t method; @@ -985,8 +656,8 @@ verification_setup_request1(uint32_t *len, struct verification_setup_context *sc return (uint8_t *)data; } -uint8_t * -verification_setup_request2(uint32_t *len, struct verification_setup_context *sctx) +static uint8_t * +pair_setup_request2(uint32_t *len, struct pair_setup_context *sctx) { plist_t dict; plist_t pk; @@ -1012,8 +683,8 @@ verification_setup_request2(uint32_t *len, struct verification_setup_context *sc return (uint8_t *)data; } -uint8_t * -verification_setup_request3(uint32_t *len, struct verification_setup_context *sctx) +static uint8_t * +pair_setup_request3(uint32_t *len, struct pair_setup_context *sctx) { plist_t dict; plist_t epk; @@ -1049,7 +720,7 @@ verification_setup_request3(uint32_t *len, struct verification_setup_context *sc return NULL; } - iv[15]++; // Magic + iv[15]++; // Nonce? /* if (iv[15] == 0x00 || iv[15] == 0xff) printf("- note that value of last byte is %d!\n", iv[15]); @@ -1075,8 +746,8 @@ verification_setup_request3(uint32_t *len, struct verification_setup_context *sc return (uint8_t *)data; } -int -verification_setup_response1(struct verification_setup_context *sctx, const uint8_t *data, uint32_t data_len) +static int +pair_setup_response1(struct pair_setup_context *sctx, const uint8_t *data, uint32_t data_len) { plist_t dict; plist_t pk; @@ -1101,8 +772,8 @@ verification_setup_response1(struct verification_setup_context *sctx, const uint return 0; } -int -verification_setup_response2(struct verification_setup_context *sctx, const uint8_t *data, uint32_t data_len) +static int +pair_setup_response2(struct pair_setup_context *sctx, const uint8_t *data, uint32_t data_len) { plist_t dict; plist_t proof; @@ -1132,8 +803,8 @@ verification_setup_response2(struct verification_setup_context *sctx, const uint return 0; } -int -verification_setup_response3(struct verification_setup_context *sctx, const uint8_t *data, uint32_t data_len) +static int +pair_setup_response3(struct pair_setup_context *sctx, const uint8_t *data, uint32_t data_len) { plist_t dict; plist_t epk; @@ -1163,92 +834,28 @@ verification_setup_response3(struct verification_setup_context *sctx, const uint plist_free(dict); + sctx->setup_is_completed = 1; return 0; } -int -verification_setup_result(const char **authorisation_key, struct verification_setup_context *sctx) +static int +pair_setup_result(const uint8_t **key, size_t *key_len, struct pair_setup_context *sctx) { - struct verification_verify_context *vctx; - char *ptr; - int i; - - if (sizeof(vctx->client_public_key) != sizeof(sctx->public_key) || sizeof(vctx->client_private_key) != sizeof(sctx->private_key)) + // Last 32 bytes of private key should match public key, but check assumption + if (memcmp(sctx->private_key + sizeof(sctx->private_key) - sizeof(sctx->public_key), sctx->public_key, sizeof(sctx->public_key)) != 0) { - sctx->errmsg = "Setup result: Bug!"; + sctx->errmsg = "Pair setup result: Unexpected keys, private key does not match public key"; return -1; } - // Fills out the auth_key with public + private in hex. It seems that the private - // key actually includes the public key (last 32 bytes), so we could in - // principle just export the private key - ptr = sctx->auth_key; - for (i = 0; i < sizeof(sctx->public_key); i++) - ptr += sprintf(ptr, "%02x", sctx->public_key[i]); - for (i = 0; i < sizeof(sctx->private_key); i++) - ptr += sprintf(ptr, "%02x", sctx->private_key[i]); - *ptr = '\0'; - - *authorisation_key = sctx->auth_key; + *key = sctx->private_key; + *key_len = sizeof(sctx->private_key); return 0; } -struct verification_verify_context * -verification_verify_new(const char *authorisation_key) -{ - struct verification_verify_context *vctx; - char hex[] = { 0, 0, 0 }; - const char *ptr; - int i; - - if (sodium_init() == -1) - return NULL; - - if (!authorisation_key) - return NULL; - - if (strlen(authorisation_key) != 2 * (sizeof(vctx->client_public_key) + sizeof(vctx->client_private_key))) - return NULL; - - vctx = calloc(1, sizeof(struct verification_verify_context)); - if (!vctx) - return NULL; - - ptr = authorisation_key; - for (i = 0; i < sizeof(vctx->client_public_key); i++, ptr+=2) - { - hex[0] = ptr[0]; - hex[1] = ptr[1]; - vctx->client_public_key[i] = strtol(hex, NULL, 16); - } - for (i = 0; i < sizeof(vctx->client_private_key); i++, ptr+=2) - { - hex[0] = ptr[0]; - hex[1] = ptr[1]; - vctx->client_private_key[i] = strtol(hex, NULL, 16); - } - - return vctx; -} - -void -verification_verify_free(struct verification_verify_context *vctx) -{ - if (!vctx) - return; - - free(vctx); -} - -const char * -verification_verify_errmsg(struct verification_verify_context *vctx) -{ - return vctx->errmsg; -} - -uint8_t * -verification_verify_request1(uint32_t *len, struct verification_verify_context *vctx) +static uint8_t * +pair_verify_request1(uint32_t *len, struct pair_verify_context *vctx) { const uint8_t basepoint[32] = {9}; uint8_t *data; @@ -1276,8 +883,8 @@ verification_verify_request1(uint32_t *len, struct verification_verify_context * return data; } -uint8_t * -verification_verify_request2(uint32_t *len, struct verification_verify_context *vctx) +static uint8_t * +pair_verify_request2(uint32_t *len, struct pair_verify_context *vctx) { uint8_t shared_secret[crypto_scalarmult_BYTES]; uint8_t key[SHA512_DIGEST_LENGTH]; @@ -1344,8 +951,8 @@ verification_verify_request2(uint32_t *len, struct verification_verify_context * return data; } -int -verification_verify_response1(struct verification_verify_context *vctx, const uint8_t *data, uint32_t data_len) +static int +pair_verify_response1(struct pair_verify_context *vctx, const uint8_t *data, uint32_t data_len) { uint32_t wanted; @@ -1361,3 +968,31 @@ verification_verify_response1(struct verification_verify_context *vctx, const ui return 0; } + +static int +pair_verify_response2(struct pair_verify_context *vctx, const uint8_t *data, uint32_t data_len) +{ + // TODO actually check response + return 0; +} + +struct pair_definition pair_fruit = +{ + .pair_setup_new = pair_setup_new, + .pair_setup_free = pair_setup_free, + .pair_setup_result = pair_setup_result, + + .pair_setup_request1 = pair_setup_request1, + .pair_setup_request2 = pair_setup_request2, + .pair_setup_request3 = pair_setup_request3, + + .pair_setup_response1 = pair_setup_response1, + .pair_setup_response2 = pair_setup_response2, + .pair_setup_response3 = pair_setup_response3, + + .pair_verify_request1 = pair_verify_request1, + .pair_verify_request2 = pair_verify_request2, + + .pair_verify_response1 = pair_verify_response1, + .pair_verify_response2 = pair_verify_response2, +}; diff --git a/src/outputs/pair_homekit.c b/src/outputs/pair_homekit.c new file mode 100644 index 00000000..68522294 --- /dev/null +++ b/src/outputs/pair_homekit.c @@ -0,0 +1,1885 @@ +/* + * Adaption of ap2-sender by ViktoriiaKh: + * + * + * To test, it is useful to try with this receiver: + * + * + * The Secure Remote Password 6a implementation is adapted from: + * - Tom Cocagne + * + * + * TLV helpers are adapted from ESP homekit: + * + * + * The MIT License (MIT) + * + * 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. + * + */ + +#include +#include +#include +#include + +#include + +#include + +#include "pair-internal.h" + + +/* ----------------------------- DEFINES ETC ------------------------------- */ + +#define USERNAME "Pair-Setup" +#define AUTHTAG_LENGTH 16 +#define NONCE_LENGTH 12 // 96 bits according to chacha poly1305 +#define REQUEST_BUFSIZE 4096 +#define ENCRYPTED_LEN_MAX 0x400 + +enum pair_keys +{ + PAIR_SETUP_MSG01 = 0, + PAIR_SETUP_MSG02, + PAIR_SETUP_MSG03, + PAIR_SETUP_MSG04, + PAIR_SETUP_MSG05, + PAIR_SETUP_MSG06, + PAIR_SETUP_SIGN, + PAIR_VERIFY_MSG01, + PAIR_VERIFY_MSG02, + PAIR_VERIFY_MSG03, + PAIR_VERIFY_MSG04, + PAIR_CONTROL_WRITE, + PAIR_CONTROL_READ, + PAIR_EVENTS_WRITE, + PAIR_EVENTS_READ, +}; + +struct pair_keys_map +{ + uint8_t state; + const char *salt; + const char *info; + const char nonce[8]; +}; + +static struct pair_keys_map pair_keys_map[] = +{ + // Used for /pair-setup + { 0x01, NULL, NULL, "" }, + { 0x02, NULL, NULL, "" }, + { 0x03, NULL, NULL, "" }, + { 0x04, NULL, NULL, "" }, + { 0x05, "Pair-Setup-Encrypt-Salt", "Pair-Setup-Encrypt-Info", "PS-Msg05" }, + { 0x06, "Pair-Setup-Encrypt-Salt", "Pair-Setup-Encrypt-Info", "PS-Msg06" }, + { 0, "Pair-Setup-Controller-Sign-Salt", "Pair-Setup-Controller-Sign-Info", "" }, + + // Used for /pair-verify + { 0x01, NULL, NULL, "" }, + { 0x02, "Pair-Verify-Encrypt-Salt", "Pair-Verify-Encrypt-Info", "PV-Msg02" }, + { 0x03, "Pair-Verify-Encrypt-Salt", "Pair-Verify-Encrypt-Info", "PV-Msg03" }, + { 0x04, NULL, NULL, "" }, + + // Encryption/decryption of control channel + { 0, "Control-Salt", "Control-Write-Encryption-Key", "" }, + { 0, "Control-Salt", "Control-Read-Encryption-Key", "" }, + + // Encryption/decryption of event channel + { 0, "Events-Salt", "Events-Write-Encryption-Key", "" }, + { 0, "Events-Salt", "Events-Read-Encryption-Key", "" }, +}; + +enum pair_method { + PairingMethodPairSetup = 0x00, + PairingMethodPairSetupWithAuth = 0x01, + PairingMethodPairVerify = 0x02, + PairingMethodAddPairing = 0x03, + PairingMethodRemovePairing = 0x04, + PairingMethodListPairings = 0x05 +}; + +enum pair_flags { + PairingFlagsTransient = 0x10, +}; + +// Forwards +const struct pair_definition pair_homekit_normal; +const struct pair_definition pair_homekit_transient; + + +/* ---------------------------------- SRP ----------------------------------- */ + +typedef enum +{ + SRP_NG_2048, + SRP_NG_3072, + SRP_NG_CUSTOM +} SRP_NGType; + +typedef struct +{ + bnum N; + bnum g; +} NGConstant; + +struct SRPUser +{ + enum hash_alg alg; + NGConstant *ng; + + bnum a; + bnum A; + bnum S; + + const unsigned char *bytes_A; + int authenticated; + + char *username; + unsigned char *password; + int password_len; + + unsigned char M [SHA512_DIGEST_LENGTH]; + unsigned char H_AMK [SHA512_DIGEST_LENGTH]; + unsigned char session_key [SHA512_DIGEST_LENGTH]; + int session_key_len; +}; + +struct NGHex +{ + const char *n_hex; + const char *g_hex; +}; + +// These constants here were pulled from Appendix A of RFC 5054 +static struct NGHex global_Ng_constants[] = +{ + { /* 2048 */ + "AC6BDB41324A9A9BF166DE5E1389582FAF72B6651987EE07FC3192943DB56050A37329CBB4" + "A099ED8193E0757767A13DD52312AB4B03310DCD7F48A9DA04FD50E8083969EDB767B0CF60" + "95179A163AB3661A05FBD5FAAAE82918A9962F0B93B855F97993EC975EEAA80D740ADBF4FF" + "747359D041D5C33EA71D281E446B14773BCA97B43A23FB801676BD207A436C6481F1D2B907" + "8717461A5B9D32E688F87748544523B524B0D57D5EA77A2775D2ECFA032CFBDBF52FB37861" + "60279004E57AE6AF874E7303CE53299CCC041C7BC308D82A5698F3A8D0C38271AE35F8E9DB" + "FBB694B5C803D89F7AE435DE236D525F54759B65E372FCD68EF20FA7111F9E4AFF73", + "2" + }, + { /* 3072 */ + "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B" + "139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485" + "B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1F" + "E649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23" + "DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32" + "905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF69558" + "17183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521" + "ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D7" + "1E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B1817" + "7B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82" + "D120A93AD2CAFFFFFFFFFFFFFFFF", + "5" + }, + {0,0} /* null sentinel */ +}; + + +static NGConstant * +new_ng(SRP_NGType ng_type, const char *n_hex, const char *g_hex) +{ + NGConstant *ng = calloc(1, sizeof(NGConstant)); + + if ( ng_type != SRP_NG_CUSTOM ) + { + n_hex = global_Ng_constants[ ng_type ].n_hex; + g_hex = global_Ng_constants[ ng_type ].g_hex; + } + + bnum_hex2bn(ng->N, n_hex); + bnum_hex2bn(ng->g, g_hex); + + return ng; +} + +static void +free_ng(NGConstant * ng) +{ + if (!ng) + return; + + bnum_free(ng->N); + bnum_free(ng->g); + free(ng); +} + +static bnum +calculate_x(enum hash_alg alg, const bnum salt, const char *username, const unsigned char *password, int password_len) +{ + unsigned char ucp_hash[SHA512_DIGEST_LENGTH]; + HashCTX ctx; + + hash_init( alg, &ctx ); + hash_update( alg, &ctx, username, strlen(username) ); + hash_update( alg, &ctx, ":", 1 ); + hash_update( alg, &ctx, password, password_len ); + hash_final( alg, &ctx, ucp_hash ); + + return H_ns( alg, salt, ucp_hash, hash_length(alg) ); +} + +static void +calculate_M(enum hash_alg alg, NGConstant *ng, unsigned char *dest, const char *I, const bnum s, + const bnum A, const bnum B, const unsigned char *K, int K_len) +{ + unsigned char H_N[ SHA512_DIGEST_LENGTH ]; + unsigned char H_g[ SHA512_DIGEST_LENGTH ]; + unsigned char H_I[ SHA512_DIGEST_LENGTH ]; + unsigned char H_xor[ SHA512_DIGEST_LENGTH ]; + HashCTX ctx; + int i = 0; + int hash_len = hash_length(alg); + + hash_num( alg, ng->N, H_N ); + hash_num( alg, ng->g, H_g ); + + hash(alg, (const unsigned char *)I, strlen(I), H_I); + + for (i=0; i < hash_len; i++ ) + H_xor[i] = H_N[i] ^ H_g[i]; + + hash_init( alg, &ctx ); + + hash_update( alg, &ctx, H_xor, hash_len ); + hash_update( alg, &ctx, H_I, hash_len ); + update_hash_n( alg, &ctx, s ); + update_hash_n( alg, &ctx, A ); + update_hash_n( alg, &ctx, B ); + hash_update( alg, &ctx, K, K_len ); + + hash_final( alg, &ctx, dest ); +} + +static void +calculate_H_AMK(enum hash_alg alg, unsigned char *dest, const bnum A, const unsigned char * M, const unsigned char * K, int K_len) +{ + HashCTX ctx; + + hash_init( alg, &ctx ); + + update_hash_n( alg, &ctx, A ); + hash_update( alg, &ctx, M, hash_length(alg) ); + hash_update( alg, &ctx, K, K_len ); + + hash_final( alg, &ctx, dest ); +} + +static struct SRPUser * +srp_user_new(enum hash_alg alg, SRP_NGType ng_type, const char *username, + const unsigned char *bytes_password, int len_password, + const char *n_hex, const char *g_hex) +{ + struct SRPUser *usr = calloc(1, sizeof(struct SRPUser)); + int ulen = strlen(username) + 1; + + if (!usr) + goto err_exit; + + usr->alg = alg; + usr->ng = new_ng( ng_type, n_hex, g_hex ); + + bnum_new(usr->a); + bnum_new(usr->A); + bnum_new(usr->S); + + if (!usr->ng || !usr->a || !usr->A || !usr->S) + goto err_exit; + + usr->username = malloc(ulen); + usr->password = malloc(len_password); + usr->password_len = len_password; + + if (!usr->username || !usr->password) + goto err_exit; + + memcpy(usr->username, username, ulen); + memcpy(usr->password, bytes_password, len_password); + + usr->authenticated = 0; + usr->bytes_A = 0; + + return usr; + + err_exit: + if (!usr) + return NULL; + + bnum_free(usr->a); + bnum_free(usr->A); + bnum_free(usr->S); + + free(usr->username); + if (usr->password) + { + memset(usr->password, 0, usr->password_len); + free(usr->password); + } + free(usr); + + return NULL; +} + +static void +srp_user_free(struct SRPUser *usr) +{ + if(!usr) + return; + + bnum_free(usr->a); + bnum_free(usr->A); + bnum_free(usr->S); + + free_ng(usr->ng); + + memset(usr->password, 0, usr->password_len); + + free(usr->username); + free(usr->password); + free((char *)usr->bytes_A); + + memset(usr, 0, sizeof(*usr)); + free(usr); +} + +static int +srp_user_is_authenticated(struct SRPUser *usr) +{ + return usr->authenticated; +} + +static const unsigned char * +srp_user_get_session_key(struct SRPUser *usr, int *key_length) +{ + if (key_length) + *key_length = usr->session_key_len; + return usr->session_key; +} + +/* Output: username, bytes_A, len_A */ +static void +srp_user_start_authentication(struct SRPUser *usr, const char **username, + const unsigned char **bytes_A, int *len_A) +{ + bnum_random(usr->a, 256); +// BN_hex2bn(&(usr->a), "D929DFB605687233C9E9030C2280156D03BDB9FDCF3CCE3BC27D9CCFCB5FF6A1"); + + bnum_modexp(usr->A, usr->ng->g, usr->a, usr->ng->N); + + *len_A = bnum_num_bytes(usr->A); + *bytes_A = malloc(*len_A); + + if (!*bytes_A) + { + *len_A = 0; + *bytes_A = 0; + *username = 0; + return; + } + + bnum_bn2bin(usr->A, (unsigned char *) *bytes_A, *len_A); + + usr->bytes_A = *bytes_A; + *username = usr->username; +} + +/* Output: bytes_M. Buffer length is SHA512_DIGEST_LENGTH */ +static void +srp_user_process_challenge(struct SRPUser *usr, const unsigned char *bytes_s, int len_s, + const unsigned char *bytes_B, int len_B, + const unsigned char **bytes_M, int *len_M ) +{ + bnum s, B, k, v; + bnum tmp1, tmp2, tmp3; + bnum u, x; + + *len_M = 0; + *bytes_M = 0; + + bnum_bin2bn(s, bytes_s, len_s); + bnum_bin2bn(B, bytes_B, len_B); + k = H_nn_pad(usr->alg, usr->ng->N, usr->ng->g); + + bnum_new(v); + bnum_new(tmp1); + bnum_new(tmp2); + bnum_new(tmp3); + + if (!s || !B || !k || !v || !tmp1 || !tmp2 || !tmp3) + goto cleanup1; + + u = H_nn_pad(usr->alg, usr->A, B); + x = calculate_x(usr->alg, s, usr->username, usr->password, usr->password_len); + if (!u || !x) + goto cleanup2; + + // SRP-6a safety check + if (!bnum_is_zero(B) && !bnum_is_zero(u)) + { + bnum_modexp(v, usr->ng->g, x, usr->ng->N); + + // S = (B - k*(g^x)) ^ (a + ux) + bnum_mul(tmp1, u, x); + bnum_add(tmp2, usr->a, tmp1); // tmp2 = (a + ux) + bnum_modexp(tmp1, usr->ng->g, x, usr->ng->N); + bnum_mul(tmp3, k, tmp1); // tmp3 = k*(g^x) + bnum_sub(tmp1, B, tmp3); // tmp1 = (B - K*(g^x)) + bnum_modexp(usr->S, tmp1, tmp2, usr->ng->N); + + hash_num(usr->alg, usr->S, usr->session_key); + usr->session_key_len = hash_length(usr->alg); + + calculate_M(usr->alg, usr->ng, usr->M, usr->username, s, usr->A, B, usr->session_key, usr->session_key_len); + calculate_H_AMK(usr->alg, usr->H_AMK, usr->A, usr->M, usr->session_key, usr->session_key_len); + + *bytes_M = usr->M; + if (len_M) + *len_M = hash_length(usr->alg); + } + else + { + *bytes_M = NULL; + if (len_M) + *len_M = 0; + } + + cleanup2: + bnum_free(x); + bnum_free(u); + cleanup1: + bnum_free(tmp3); + bnum_free(tmp2); + bnum_free(tmp1); + bnum_free(v); + bnum_free(k); + bnum_free(B); + bnum_free(s); +} + +static void +srp_user_verify_session(struct SRPUser *usr, const unsigned char *bytes_HAMK) +{ + if (memcmp(usr->H_AMK, bytes_HAMK, hash_length(usr->alg)) == 0) + usr->authenticated = 1; +} + + +/* ---------------------------------- TLV ----------------------------------- */ + +#define TLV_ERROR_MEMORY -1 +#define TLV_ERROR_INSUFFICIENT_SIZE -2 + +typedef enum { + TLVType_Method = 0, // (integer) Method to use for pairing. See PairMethod + TLVType_Identifier = 1, // (UTF-8) Identifier for authentication + TLVType_Salt = 2, // (bytes) 16+ bytes of random salt + TLVType_PublicKey = 3, // (bytes) Curve25519, SRP public key or signed Ed25519 key + TLVType_Proof = 4, // (bytes) Ed25519 or SRP proof + TLVType_EncryptedData = 5, // (bytes) Encrypted data with auth tag at end + TLVType_State = 6, // (integer) State of the pairing process. 1=M1, 2=M2, etc. + TLVType_Error = 7, // (integer) Error code. Must only be present if error code is + // not 0. See TLVError + TLVType_RetryDelay = 8, // (integer) Seconds to delay until retrying a setup code + TLVType_Certificate = 9, // (bytes) X.509 Certificate + TLVType_Signature = 10, // (bytes) Ed25519 + TLVType_Permissions = 11, // (integer) Bit value describing permissions of the controller + // being added. + // None (0x00): Regular user + // Bit 1 (0x01): Admin that is able to add and remove + // pairings against the accessory + TLVType_FragmentData = 13, // (bytes) Non-last fragment of data. If length is 0, + // it's an ACK. + TLVType_FragmentLast = 14, // (bytes) Last fragment of data + TLVType_Flags = 19, // Added from airplay2_receiver + TLVType_Separator = 0xff, +} TLVType; + + +typedef enum { + TLVMethod_PairSetup = 1, + TLVMethod_PairVerify = 2, + TLVMethod_AddPairing = 3, + TLVMethod_RemovePairing = 4, + TLVMethod_ListPairings = 5, +} TLVMethod; + + +typedef enum { + TLVError_Unknown = 1, // Generic error to handle unexpected errors + TLVError_Authentication = 2, // Setup code or signature verification failed + TLVError_Backoff = 3, // Client must look at the retry delay TLV item and + // wait that many seconds before retrying + TLVError_MaxPeers = 4, // Server cannot accept any more pairings + TLVError_MaxTries = 5, // Server reached its maximum number of + // authentication attempts + TLVError_Unavailable = 6, // Server pairing method is unavailable + TLVError_Busy = 7, // Server is busy and cannot accept a pairing + // request at this time +} TLVError; + +typedef struct _tlv { + struct _tlv *next; + uint8_t type; + uint8_t *value; + size_t size; +} tlv_t; + + +typedef struct { + tlv_t *head; +} tlv_values_t; + + +static tlv_values_t * +tlv_new() { + tlv_values_t *values = malloc(sizeof(tlv_values_t)); + if (!values) + return NULL; + + values->head = NULL; + return values; +} + +static void +tlv_free(tlv_values_t *values) { + tlv_t *t = values->head; + while (t) { + tlv_t *t2 = t; + t = t->next; + if (t2->value) + free(t2->value); + free(t2); + } + free(values); +} + +static int +tlv_add_value_(tlv_values_t *values, uint8_t type, uint8_t *value, size_t size) { + tlv_t *tlv = malloc(sizeof(tlv_t)); + if (!tlv) { + return TLV_ERROR_MEMORY; + } + tlv->type = type; + tlv->size = size; + tlv->value = value; + tlv->next = NULL; + + if (!values->head) { + values->head = tlv; + } else { + tlv_t *t = values->head; + while (t->next) { + t = t->next; + } + t->next = tlv; + } + + return 0; +} + +static int +tlv_add_value(tlv_values_t *values, uint8_t type, const uint8_t *value, size_t size) { + uint8_t *data = NULL; + int ret; + if (size) { + data = malloc(size); + if (!data) { + return TLV_ERROR_MEMORY; + } + memcpy(data, value, size); + } + ret = tlv_add_value_(values, type, data, size); + if (ret < 0) + free(data); + return ret; +} + +static tlv_t * +tlv_get_value(const tlv_values_t *values, uint8_t type) { + tlv_t *t = values->head; + while (t) { + if (t->type == type) + return t; + t = t->next; + } + return NULL; +} + +static int +tlv_format(const tlv_values_t *values, uint8_t *buffer, size_t *size) { + size_t required_size = 0; + tlv_t *t = values->head; + while (t) { + required_size += t->size + 2 * ((t->size + 254) / 255); + t = t->next; + } + + if (*size < required_size) { + *size = required_size; + return TLV_ERROR_INSUFFICIENT_SIZE; + } + + *size = required_size; + + t = values->head; + while (t) { + uint8_t *data = t->value; + if (!t->size) { + buffer[0] = t->type; + buffer[1] = 0; + buffer += 2; + t = t->next; + continue; + } + + size_t remaining = t->size; + + while (remaining) { + buffer[0] = t->type; + size_t chunk_size = (remaining > 255) ? 255 : remaining; + buffer[1] = chunk_size; + memcpy(&buffer[2], data, chunk_size); + remaining -= chunk_size; + buffer += chunk_size + 2; + data += chunk_size; + } + + t = t->next; + } + + return 0; +} + +static int +tlv_parse(const uint8_t *buffer, size_t length, tlv_values_t *values) { + size_t i = 0; + int ret; + while (i < length) { + uint8_t type = buffer[i]; + size_t size = 0; + uint8_t *data = NULL; + + // scan TLVs to accumulate total size of subsequent TLVs with same type (chunked data) + size_t j = i; + while (j < length && buffer[j] == type && buffer[j+1] == 255) { + size_t chunk_size = buffer[j+1]; + size += chunk_size; + j += chunk_size + 2; + } + if (j < length && buffer[j] == type) { + size_t chunk_size = buffer[j+1]; + size += chunk_size; + } + + // allocate memory to hold all pieces of chunked data and copy data there + if (size != 0) { + data = malloc(size); + if (!data) + return TLV_ERROR_MEMORY; + + uint8_t *p = data; + + size_t remaining = size; + while (remaining) { + size_t chunk_size = buffer[i+1]; + memcpy(p, &buffer[i+2], chunk_size); + p += chunk_size; + i += chunk_size + 2; + remaining -= chunk_size; + } + } + + ret = tlv_add_value_(values, type, data, size); + if (ret < 0) { + free(data); + return ret; + } + } + + return 0; +} + + +/* -------------------------------- HELPERS --------------------------------- */ + +#ifdef DEBUG_PAIR +static void +tlv_debug(const tlv_values_t *values) +{ + printf("Received TLV values\n"); + for (tlv_t *t=values->head; t; t=t->next) + { + printf("Type %d value (%zu bytes): \n", t->type, t->size); + hexdump("", t->value, t->size); + } +} +#endif + +static tlv_values_t * +response_process(const uint8_t *data, uint32_t data_len, const char **errmsg) +{ + tlv_values_t *response; + tlv_t *error; + int ret; + + response = tlv_new(); + if (!response) + { + *errmsg = "Out of memory\n"; + return NULL; + } + + ret = tlv_parse(data, data_len, response); + if (ret < 0) + { + *errmsg = "Could not parse TLV\n"; + goto error; + } + +#ifdef DEBUG_PAIR + tlv_debug(response); +#endif + + error = tlv_get_value(response, TLVType_Error); + if (error) + { + if (error->value[0] == TLVError_Authentication) + *errmsg = "Device returned an authtication failure"; + else if (error->value[0] == TLVError_Backoff) + *errmsg = "Device told us to back off pairing attempts\n"; + else if (error->value[0] == TLVError_MaxPeers) + *errmsg = "Max peers trying to connect to device\n"; + else if (error->value[0] == TLVError_MaxTries) + *errmsg = "Max pairing attemps reached\n"; + else if (error->value[0] == TLVError_Unavailable) + *errmsg = "Device is unuavailble at this time\n"; + else + *errmsg = "Device is busy/returned unknown error\n"; + + goto error; + } + + return response; + + error: + tlv_free(response); + return NULL; +} + +/* Executes SHA512 RFC 5869 extract + expand, writing a derived key to okm + + hkdfExtract(SHA512, salt, salt_len, ikm, ikm_len, prk); + hkdfExpand(SHA512, prk, SHA512_LEN, info, info_len, okm, okm_len); +*/ +static int +hkdf_extract_expand(uint8_t *okm, size_t okm_len, const uint8_t *ikm, size_t ikm_len, enum pair_keys pair_key) +{ +#ifdef CONFIG_OPENSSL +#include + EVP_PKEY_CTX *pctx; + + if (okm_len > SHA512_DIGEST_LENGTH) + return -1; + if (! (pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL))) + return -1; + if (EVP_PKEY_derive_init(pctx) <= 0) + goto error; + if (EVP_PKEY_CTX_set_hkdf_md(pctx, EVP_sha512()) <= 0) + goto error; + if (EVP_PKEY_CTX_set1_hkdf_salt(pctx, (const unsigned char *)pair_keys_map[pair_key].salt, strlen(pair_keys_map[pair_key].salt)) <= 0) + goto error; + if (EVP_PKEY_CTX_set1_hkdf_key(pctx, ikm, ikm_len) <= 0) + goto error; + if (EVP_PKEY_CTX_add1_hkdf_info(pctx, (const unsigned char *)pair_keys_map[pair_key].info, strlen(pair_keys_map[pair_key].info)) <= 0) + goto error; + if (EVP_PKEY_derive(pctx, okm, &okm_len) <= 0) + goto error; + + EVP_PKEY_CTX_free(pctx); + return 0; + + error: + EVP_PKEY_CTX_free(pctx); + return -1; +#elif CONFIG_GCRYPT + uint8_t prk[SHA512_DIGEST_LENGTH]; + gcry_md_hd_t hmac_handle; + + if (okm_len > SHA512_DIGEST_LENGTH) + return -1; // Below calculation not valid if output is larger than hash size + if (gcry_md_open(&hmac_handle, GCRY_MD_SHA512, GCRY_MD_FLAG_HMAC) != GPG_ERR_NO_ERROR) + return -1; + if (gcry_md_setkey(hmac_handle, (const unsigned char *)pair_keys_map[pair_key].salt, strlen(pair_keys_map[pair_key].salt)) != GPG_ERR_NO_ERROR) + goto error; + gcry_md_write(hmac_handle, ikm, ikm_len); + memcpy(prk, gcry_md_read(hmac_handle, 0), sizeof(prk)); + + gcry_md_reset(hmac_handle); + + if (gcry_md_setkey(hmac_handle, prk, sizeof(prk)) != GPG_ERR_NO_ERROR) + goto error; + gcry_md_write(hmac_handle, (const unsigned char *)pair_keys_map[pair_key].info, strlen(pair_keys_map[pair_key].info)); + gcry_md_putc(hmac_handle, 1); + + memcpy(okm, gcry_md_read(hmac_handle, 0), okm_len); + + gcry_md_close(hmac_handle); + return 0; + + error: + gcry_md_close(hmac_handle); + return -1; +#else + return -1; +#endif +} + +static int +encrypt_chacha(uint8_t *cipher, uint8_t *plain, size_t plain_len, const uint8_t *key, size_t key_len, const void *ad, size_t ad_len, uint8_t *tag, size_t tag_len, const uint8_t nonce[NONCE_LENGTH]) +{ +#ifdef CONFIG_OPENSSL + EVP_CIPHER_CTX *ctx; + int len; + + if (! (ctx = EVP_CIPHER_CTX_new())) + return -1; + + if (EVP_EncryptInit_ex(ctx, EVP_chacha20_poly1305(), NULL, key, nonce) != 1) + goto error; + + if (EVP_CIPHER_CTX_set_padding(ctx, 0) != 1) // Maybe not necessary + goto error; + + if (EVP_EncryptUpdate(ctx, NULL, &len, ad, ad_len) != 1) + goto error; + + if (EVP_EncryptUpdate(ctx, cipher, &len, plain, plain_len) != 1) + goto error; + + assert(len == plain_len); + + if (EVP_EncryptFinal_ex(ctx, NULL, &len) != 1) + goto error; + + if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_GET_TAG, tag_len, tag) != 1) + goto error; + + EVP_CIPHER_CTX_free(ctx); + return 0; + + error: + EVP_CIPHER_CTX_free(ctx); + return -1; +#elif CONFIG_GCRYPT + gcry_cipher_hd_t hd; + + if (gcry_cipher_open(&hd, GCRY_CIPHER_CHACHA20, GCRY_CIPHER_MODE_POLY1305, 0) != GPG_ERR_NO_ERROR) + return -1; + + if (gcry_cipher_setkey(hd, key, key_len) != GPG_ERR_NO_ERROR) + goto error; + + if (gcry_cipher_setiv(hd, nonce, NONCE_LENGTH) != GPG_ERR_NO_ERROR) + goto error; + + if (gcry_cipher_authenticate(hd, ad, ad_len) != GPG_ERR_NO_ERROR) + goto error; + + if (gcry_cipher_encrypt(hd, cipher, plain_len, plain, plain_len) != GPG_ERR_NO_ERROR) + goto error; + + if (gcry_cipher_gettag(hd, tag, tag_len) != GPG_ERR_NO_ERROR) + goto error; + + gcry_cipher_close(hd); + return 0; + + error: + gcry_cipher_close(hd); + return -1; +#else + return -1; +#endif +} + +static int +decrypt_chacha(uint8_t *plain, uint8_t *cipher, size_t cipher_len, const uint8_t *key, size_t key_len, const void *ad, size_t ad_len, uint8_t *tag, size_t tag_len, const uint8_t nonce[NONCE_LENGTH]) +{ +#ifdef CONFIG_OPENSSL + EVP_CIPHER_CTX *ctx; + int len; + + if (! (ctx = EVP_CIPHER_CTX_new())) + return -1; + + if (EVP_DecryptInit_ex(ctx, EVP_chacha20_poly1305(), NULL, key, nonce) != 1) + goto error; + + if (EVP_CIPHER_CTX_set_padding(ctx, 0) != 1) // Maybe not necessary + goto error; + + if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, tag_len, tag) != 1) + goto error; + + if (EVP_DecryptUpdate(ctx, NULL, &len, ad, ad_len) != 1) + goto error; + + if (EVP_DecryptUpdate(ctx, plain, &len, cipher, cipher_len) != 1) + goto error; + + assert(len == cipher_len); + + if (EVP_DecryptFinal_ex(ctx, NULL, &len) != 1) + goto error; + + EVP_CIPHER_CTX_free(ctx); + return 0; + + error: + EVP_CIPHER_CTX_free(ctx); + return -1; +#elif CONFIG_GCRYPT + gcry_cipher_hd_t hd; + + if (gcry_cipher_open(&hd, GCRY_CIPHER_CHACHA20, GCRY_CIPHER_MODE_POLY1305, 0) != GPG_ERR_NO_ERROR) + return -1; + + if (gcry_cipher_setkey(hd, key, key_len) != GPG_ERR_NO_ERROR) + goto error; + + if (gcry_cipher_setiv(hd, nonce, NONCE_LENGTH) != GPG_ERR_NO_ERROR) + goto error; + + if (gcry_cipher_authenticate(hd, ad, ad_len) != GPG_ERR_NO_ERROR) + goto error; + + if (gcry_cipher_decrypt(hd, plain, cipher_len, cipher, cipher_len) != GPG_ERR_NO_ERROR) + goto error; + + if (gcry_cipher_checktag(hd, tag, tag_len) != GPG_ERR_NO_ERROR) + goto error; + + gcry_cipher_close(hd); + return 0; + + error: + gcry_cipher_close(hd); + return -1; +#else + return -1; +#endif +} + +static int +create_and_sign_device_info(uint8_t *data, size_t *data_len, const char *device_id, uint8_t *device_pk, size_t device_pk_len, uint8_t *pk, size_t pk_len, uint8_t *sk) +{ + tlv_values_t *tlv; + uint8_t *device_info; + uint32_t device_info_len; + size_t device_id_len; + uint8_t signature[crypto_sign_BYTES]; + int ret; + + device_id_len = strlen(device_id); + + device_info_len = device_pk_len + device_id_len + pk_len; + device_info = malloc(device_info_len); + + memcpy(device_info, device_pk, device_pk_len); + memcpy(device_info + device_pk_len, device_id, device_id_len); + memcpy(device_info + device_pk_len + device_id_len, pk, pk_len); + + crypto_sign_detached(signature, NULL, device_info, device_info_len, sk); + free(device_info); + + tlv = tlv_new(); + tlv_add_value(tlv, TLVType_Identifier, (unsigned char *)device_id, device_id_len); + tlv_add_value(tlv, TLVType_Signature, signature, sizeof(signature)); + + ret = tlv_format(tlv, data, data_len); + + tlv_free(tlv); + return ret; +} + + +/* -------------------------- IMPLEMENTATION -------------------------------- */ + +static struct pair_setup_context * +pair_setup_new(struct pair_definition *type, const char *pin, const char *device_id) +{ + struct pair_setup_context *sctx; + + if (sodium_init() == -1) + return NULL; + + if (type == &pair_homekit_normal) + { + if (!pin || strlen(pin) < 4) + return NULL; + } + else if (type == &pair_homekit_transient && !pin) + { + pin = "3939"; + } + + if (device_id && strlen(device_id) != 16) + return NULL; + + sctx = calloc(1, sizeof(struct pair_setup_context)); + if (!sctx) + return NULL; + + sctx->type = type; + + memcpy(sctx->pin, pin, sizeof(sctx->pin)); + + if (device_id) + memcpy(sctx->device_id, device_id, strlen(device_id)); + + return sctx; +} + +static void +pair_setup_free(struct pair_setup_context *sctx) +{ + if (!sctx) + return; + + srp_user_free(sctx->user); + + free(sctx->pkB); + free(sctx->M2); + free(sctx->salt); + free(sctx->epk); + free(sctx->authtag); + + free(sctx); +} + +static uint8_t * +pair_setup_request1(uint32_t *len, struct pair_setup_context *sctx) +{ + tlv_values_t *request; + uint8_t *data; + size_t data_len; + uint8_t method; + uint8_t flags; + int endian_test = 1; + int ret; + + data_len = REQUEST_BUFSIZE; + data = malloc(data_len); + request = tlv_new(); + + // Test here instead of setup_new() so we can give an error message + if(*(char *)&endian_test != 1) + { + sctx->errmsg = "Setup request 1: No support for big endian architechture"; + goto error; + } + + sctx->user = srp_user_new(HASH_SHA512, SRP_NG_3072, USERNAME, (unsigned char *)sctx->pin, sizeof(sctx->pin), 0, 0); + if (!sctx->user) + { + sctx->errmsg = "Setup request 1: Create SRP user failed"; + goto error; + } + + method = PairingMethodPairSetup; + tlv_add_value(request, TLVType_State, &pair_keys_map[PAIR_SETUP_MSG01].state, sizeof(pair_keys_map[PAIR_SETUP_MSG01].state)); + tlv_add_value(request, TLVType_Method, &method, sizeof(method)); + + if (sctx->type == &pair_homekit_transient) + { + flags = PairingFlagsTransient; + tlv_add_value(request, TLVType_Flags, &flags, sizeof(flags)); + } + + ret = tlv_format(request, data, &data_len); + if (ret < 0) + { + sctx->errmsg = "Setup request 1: tlv_format returned an error"; + goto error; + } + + *len = data_len; + + tlv_free(request); + return data; + + error: + tlv_free(request); + free(data); + return NULL; +} + +static uint8_t * +pair_setup_request2(uint32_t *len, struct pair_setup_context *sctx) +{ + tlv_values_t *request; + uint8_t *data; + size_t data_len; + const char *auth_username = NULL; + int ret; + + data_len = REQUEST_BUFSIZE; + data = malloc(data_len); + request = tlv_new(); + + // Calculate A + srp_user_start_authentication(sctx->user, &auth_username, &sctx->pkA, &sctx->pkA_len); + + // Calculate M1 (client proof) + srp_user_process_challenge(sctx->user, (const unsigned char *)sctx->salt, sctx->salt_len, (const unsigned char *)sctx->pkB, sctx->pkB_len, &sctx->M1, &sctx->M1_len); + + tlv_add_value(request, TLVType_State, &pair_keys_map[PAIR_SETUP_MSG03].state, sizeof(pair_keys_map[PAIR_SETUP_MSG03].state)); + tlv_add_value(request, TLVType_PublicKey, sctx->pkA, sctx->pkA_len); + tlv_add_value(request, TLVType_Proof, sctx->M1, sctx->M1_len); + + ret = tlv_format(request, data, &data_len); + if (ret < 0) + { + sctx->errmsg = "Setup request 2: tlv_format returned an error"; + goto error; + } + + *len = data_len; + + tlv_free(request); + return data; + + error: + tlv_free(request); + free(data); + return NULL; +} + +static uint8_t * +pair_setup_request3(uint32_t *len, struct pair_setup_context *sctx) +{ + tlv_values_t *request; + uint8_t *data; + size_t data_len; + const unsigned char *session_key; + int session_key_len; + uint8_t device_x[32]; + uint8_t nonce[NONCE_LENGTH] = { 0 }; + uint8_t tag[AUTHTAG_LENGTH]; + uint8_t derived_key[32]; + tlv_values_t *append; + size_t append_len; + uint8_t *encrypted_data = NULL; + size_t encrypted_data_len; + int ret; + + data_len = REQUEST_BUFSIZE; + data = malloc(data_len); + request = tlv_new(); + + session_key = srp_user_get_session_key(sctx->user, &session_key_len); + if (!session_key) + { + sctx->errmsg = "Setup request 3: No valid session key"; + goto error; + } + + ret = hkdf_extract_expand(device_x, sizeof(device_x), session_key, session_key_len, PAIR_SETUP_SIGN); + if (ret < 0) + { + sctx->errmsg = "Setup request 3: hkdf error getting device_x"; + goto error; + } + + crypto_sign_keypair(sctx->public_key, sctx->private_key); + + ret = create_and_sign_device_info(data, &data_len, sctx->device_id, device_x, sizeof(device_x), sctx->public_key, sizeof(sctx->public_key), sctx->private_key); + if (ret < 0) + { + sctx->errmsg = "Setup request 3: error creating signed device info"; + goto error; + } + + ret = hkdf_extract_expand(derived_key, sizeof(derived_key), session_key, 64, PAIR_SETUP_MSG05); // TODO is session_key_len always 64? + if (ret < 0) + { + sctx->errmsg = "Setup request 3: hkdf error getting derived_key"; + goto error; + } + + // Append TLV-encoded public key to *data, which already has identifier and signature + append = tlv_new(); + append_len = REQUEST_BUFSIZE - data_len; + tlv_add_value(append, TLVType_PublicKey, sctx->public_key, sizeof(sctx->public_key)); + ret = tlv_format(append, data + data_len, &append_len); + tlv_free(append); + if (ret < 0) + { + sctx->errmsg = "Setup request 3: error appending public key to TLV"; + goto error; + } + data_len += append_len; + + memcpy(nonce + 4, pair_keys_map[PAIR_SETUP_MSG05].nonce, NONCE_LENGTH - 4); + + encrypted_data_len = data_len + sizeof(tag); // Space for ciphered payload and authtag + encrypted_data = malloc(encrypted_data_len); + + ret = encrypt_chacha(encrypted_data, data, data_len, derived_key, sizeof(derived_key), NULL, 0, tag, sizeof(tag), nonce); + if (ret < 0) + { + sctx->errmsg = "Setup request 3: Could not encrypt"; + goto error; + } + + memcpy(encrypted_data + data_len, tag, sizeof(tag)); + + tlv_add_value(request, TLVType_State, &pair_keys_map[PAIR_SETUP_MSG05].state, sizeof(pair_keys_map[PAIR_SETUP_MSG05].state)); + tlv_add_value(request, TLVType_EncryptedData, encrypted_data, encrypted_data_len); + + data_len = REQUEST_BUFSIZE; // Re-using *data, so pass original length to tlv_format + ret = tlv_format(request, data, &data_len); + if (ret < 0) + { + sctx->errmsg = "Setup request 3: error appending public key to TLV"; + goto error; + } + + *len = data_len; + + free(encrypted_data); + tlv_free(request); + return data; + + error: + free(encrypted_data); + tlv_free(request); + free(data); + return NULL; +} + +static int +pair_setup_response1(struct pair_setup_context *sctx, const uint8_t *data, uint32_t data_len) +{ + tlv_values_t *response; + tlv_t *pk; + tlv_t *salt; + + response = response_process(data, data_len, &sctx->errmsg); + if (!response) + { + sctx->errmsg = "Setup response 1: Could not parse TLV"; + return -1; + } + + pk = tlv_get_value(response, TLVType_PublicKey); + salt = tlv_get_value(response, TLVType_Salt); + if (!pk || !salt) + { + sctx->errmsg = "Setup response 1: Missing or invalid pk/salt"; + goto error; + } + + sctx->pkB_len = pk->size; // 384 + sctx->pkB = malloc(sctx->pkB_len); + memcpy(sctx->pkB, pk->value, sctx->pkB_len); + + sctx->salt_len = salt->size; // 16 + sctx->salt = malloc(sctx->salt_len); + memcpy(sctx->salt, salt->value, sctx->salt_len); + + tlv_free(response); + return 0; + + error: + tlv_free(response); + return -1; +} + +static int +pair_setup_response2(struct pair_setup_context *sctx, const uint8_t *data, uint32_t data_len) +{ + tlv_values_t *response; + tlv_t *proof; + + response = response_process(data, data_len, &sctx->errmsg); + if (!response) + { + sctx->errmsg = "Setup response 2: Could not parse TLV"; + return -1; + } + + proof = tlv_get_value(response, TLVType_Proof); + if (!proof) + { + sctx->errmsg = "Setup response 2: Missing proof"; + goto error; + } + + sctx->M2_len = proof->size; // 64 + sctx->M2 = malloc(sctx->M2_len); + memcpy(sctx->M2, proof->value, sctx->M2_len); + + // Check M2 + srp_user_verify_session(sctx->user, (const unsigned char *)sctx->M2); + if (!srp_user_is_authenticated(sctx->user)) + { + sctx->errmsg = "Setup response 2: Server authentication failed"; + goto error; + } + + tlv_free(response); + + if (sctx->type == &pair_homekit_transient) + sctx->setup_is_completed = 1; + + return 0; + + error: + tlv_free(response); + return -1; +} + +static int +pair_setup_response3(struct pair_setup_context *sctx, const uint8_t *data, uint32_t data_len) +{ + tlv_values_t *response; + tlv_t *encrypted_data; + uint8_t nonce[NONCE_LENGTH] = { 0 }; + uint8_t tag[AUTHTAG_LENGTH]; + uint8_t derived_key[32]; + size_t encrypted_len; + uint8_t *decrypted_data = NULL; + const uint8_t *session_key; + int session_key_len; + int ret; + + response = response_process(data, data_len, &sctx->errmsg); + if (!response) + { + sctx->errmsg = "Setup response 3: Could not parse TLV"; + return -1; + } + + encrypted_data = tlv_get_value(response, TLVType_EncryptedData); + if (!encrypted_data) + { + sctx->errmsg = "Setup response 3: Missing encrypted_data"; + goto error; + } + + session_key = srp_user_get_session_key(sctx->user, &session_key_len); + if (!session_key) + { + sctx->errmsg = "Setup response 3: No valid session key"; + goto error; + } + + ret = hkdf_extract_expand(derived_key, sizeof(derived_key), session_key, 64, PAIR_SETUP_MSG06); // TODO is session_key_len always 64? + if (ret < 0) + { + sctx->errmsg = "Setup response 3: hkdf error getting derived_key"; + goto error; + } + + // encrypted_data->value consists of the encrypted payload + the auth tag + if (encrypted_data->size < AUTHTAG_LENGTH) + { + sctx->errmsg = "Setup response 3: Invalid encrypted data"; + goto error; + } + + encrypted_len = encrypted_data->size - AUTHTAG_LENGTH; + memcpy(tag, encrypted_data->value + encrypted_len, AUTHTAG_LENGTH); + memcpy(nonce + 4, pair_keys_map[PAIR_SETUP_MSG06].nonce, NONCE_LENGTH - 4); + + decrypted_data = malloc(encrypted_len); + + ret = decrypt_chacha(decrypted_data, encrypted_data->value, encrypted_len, derived_key, sizeof(derived_key), NULL, 0, tag, sizeof(tag), nonce); + if (ret < 0) + { + sctx->errmsg = "Setup response 3: Decryption error"; + goto error; + } + + tlv_free(response); + response = response_process(decrypted_data, encrypted_len, &sctx->errmsg); + if (!response) + { + sctx->errmsg = "Setup response 3: Could not parse decrypted TLV"; + goto error; + } + + // TODO check identifier and signature - we get an identifier (36), a public key (32) and a signature (64) + + free(decrypted_data); + tlv_free(response); + + sctx->setup_is_completed = 1; + return 0; + + error: + free(decrypted_data); + tlv_free(response); + return -1; +} + +static int +pair_setup_result(const uint8_t **key, size_t *key_len, struct pair_setup_context *sctx) +{ + const uint8_t *session_key; + int session_key_len; + + if (sctx->type == &pair_homekit_normal) + { + // Last 32 bytes of private key should match public key, but check assumption + if (memcmp(sctx->private_key + sizeof(sctx->private_key) - sizeof(sctx->public_key), sctx->public_key, sizeof(sctx->public_key)) != 0) + { + sctx->errmsg = "Pair setup result: Unexpected keys, private key does not match public key"; + return -1; + } + *key = sctx->private_key; + *key_len = sizeof(sctx->private_key); + return 0; + } + if (sctx->type == &pair_homekit_transient) + { + session_key = srp_user_get_session_key(sctx->user, &session_key_len); + *key = session_key; + *key_len = session_key_len; + return 0; + } + + return -1; +} + +static uint8_t * +pair_verify_request1(uint32_t *len, struct pair_verify_context *vctx) +{ + const uint8_t basepoint[32] = {9}; + tlv_values_t *request; + uint8_t *data; + size_t data_len; + int ret; + + data_len = REQUEST_BUFSIZE; + data = malloc(data_len); + request = tlv_new(); + + ret = crypto_scalarmult(vctx->client_eph_public_key, vctx->client_eph_private_key, basepoint); + if (ret < 0) + { + vctx->errmsg = "Verify request 1: Curve 25519 returned an error"; + goto error; + } + + tlv_add_value(request, TLVType_State, &pair_keys_map[PAIR_VERIFY_MSG01].state, sizeof(pair_keys_map[PAIR_VERIFY_MSG01].state)); + tlv_add_value(request, TLVType_PublicKey, vctx->client_eph_public_key, sizeof(vctx->client_eph_public_key)); + + ret = tlv_format(request, data, &data_len); + if (ret < 0) + { + vctx->errmsg = "Verify request 1: tlv_format returned an error"; + goto error; + } + + *len = data_len; + + tlv_free(request); + return data; + + error: + tlv_free(request); + free(data); + return NULL; +} + +static uint8_t * +pair_verify_request2(uint32_t *len, struct pair_verify_context *vctx) +{ + tlv_values_t *request; + uint8_t *data; + size_t data_len; + uint8_t nonce[NONCE_LENGTH] = { 0 }; + uint8_t tag[AUTHTAG_LENGTH]; + uint8_t derived_key[32]; + uint8_t *encrypted_data = NULL; + size_t encrypted_data_len; + int ret; + + data_len = REQUEST_BUFSIZE; + data = malloc(data_len); + request = tlv_new(); + + ret = create_and_sign_device_info(data, &data_len, vctx->device_id, vctx->client_eph_public_key, sizeof(vctx->client_eph_public_key), + vctx->server_eph_public_key, sizeof(vctx->server_eph_public_key), vctx->client_private_key); + if (ret < 0) + { + vctx->errmsg = "Verify request 2: error creating signed device info"; + goto error; + } + + ret = hkdf_extract_expand(derived_key, sizeof(derived_key), vctx->shared_secret, sizeof(vctx->shared_secret), PAIR_VERIFY_MSG03); + if (ret < 0) + { + vctx->errmsg = "Verify request 2: hkdf error getting derived_key"; + goto error; + } + + memcpy(nonce + 4, pair_keys_map[PAIR_VERIFY_MSG03].nonce, NONCE_LENGTH - 4); + + encrypted_data_len = data_len + sizeof(tag); // Space for ciphered payload and authtag + encrypted_data = malloc(encrypted_data_len); + + ret = encrypt_chacha(encrypted_data, data, data_len, derived_key, sizeof(derived_key), NULL, 0, tag, sizeof(tag), nonce); + if (ret < 0) + { + vctx->errmsg = "Verify request 2: Could not encrypt"; + goto error; + } + + memcpy(encrypted_data + data_len, tag, sizeof(tag)); + + tlv_add_value(request, TLVType_State, &pair_keys_map[PAIR_VERIFY_MSG03].state, sizeof(pair_keys_map[PAIR_VERIFY_MSG03].state)); + tlv_add_value(request, TLVType_EncryptedData, encrypted_data, encrypted_data_len); + + data_len = REQUEST_BUFSIZE; // Re-using *data, so pass original length to tlv_format + ret = tlv_format(request, data, &data_len); + if (ret < 0) + { + vctx->errmsg = "Verify request 2: tlv_format returned an error"; + goto error; + } + + *len = data_len; + + free(encrypted_data); + tlv_free(request); + return data; + + error: + free(encrypted_data); + tlv_free(request); + free(data); + return NULL; +} + +static int +pair_verify_response1(struct pair_verify_context *vctx, const uint8_t *data, uint32_t data_len) +{ + tlv_values_t *response; + tlv_t *encrypted_data; + tlv_t *public_key; + uint8_t nonce[NONCE_LENGTH] = { 0 }; + uint8_t tag[AUTHTAG_LENGTH]; + uint8_t derived_key[32]; + size_t encrypted_len; + uint8_t *decrypted_data = NULL; + int ret; + + response = response_process(data, data_len, &vctx->errmsg); + if (!response) + { + vctx->errmsg = "Verify response 1: Could not parse TLV"; + return -1; + } + + encrypted_data = tlv_get_value(response, TLVType_EncryptedData); + if (!encrypted_data) + { + vctx->errmsg = "Verify response 1: Missing encrypted_data"; + goto error; + } + + public_key = tlv_get_value(response, TLVType_PublicKey); + if (!public_key || public_key->size != sizeof(vctx->server_eph_public_key)) + { + vctx->errmsg = "Verify response 1: Missing or invalid public_key"; + goto error; + } + + memcpy(vctx->server_eph_public_key, public_key->value, sizeof(vctx->server_eph_public_key)); + ret = crypto_scalarmult(vctx->shared_secret, vctx->client_eph_private_key, vctx->server_eph_public_key); + if (ret < 0) + { + vctx->errmsg = "Verify response 1: Curve 25519 returned an error"; + goto error; + } + + ret = hkdf_extract_expand(derived_key, sizeof(derived_key), vctx->shared_secret, sizeof(vctx->shared_secret), PAIR_VERIFY_MSG02); + if (ret < 0) + { + vctx->errmsg = "Verify response 1: hkdf error getting derived_key"; + goto error; + } + + // encrypted_data->value consists of the encrypted payload + the auth tag + if (encrypted_data->size < AUTHTAG_LENGTH) + { + vctx->errmsg = "Verify response 1: Invalid encrypted data"; + goto error; + } + + encrypted_len = encrypted_data->size - AUTHTAG_LENGTH; + memcpy(tag, encrypted_data->value + encrypted_len, AUTHTAG_LENGTH); + memcpy(nonce + 4, pair_keys_map[PAIR_VERIFY_MSG02].nonce, NONCE_LENGTH - 4); + + decrypted_data = malloc(encrypted_len); + + ret = decrypt_chacha(decrypted_data, encrypted_data->value, encrypted_len, derived_key, sizeof(derived_key), NULL, 0, tag, sizeof(tag), nonce); + if (ret < 0) + { + vctx->errmsg = "Verify response 1: Decryption error"; + goto error; + } + + tlv_free(response); + response = response_process(decrypted_data, encrypted_len, &vctx->errmsg); + if (!response) + { + vctx->errmsg = "Verify response 1: Could not parse decrypted TLV"; + goto error; + } + + // TODO check identifier and signature + + free(decrypted_data); + tlv_free(response); + return 0; + + error: + free(decrypted_data); + tlv_free(response); + return -1; +} + +static int +pair_verify_response2(struct pair_verify_context *vctx, const uint8_t *data, uint32_t data_len) +{ + // TODO actually check response + return 0; +} + +static void +pair_cipher_free(struct pair_cipher_context *cctx) +{ + if (!cctx) + return; + + free(cctx); +} + +static struct pair_cipher_context * +pair_cipher_new(struct pair_definition *type, int channel, const uint8_t *shared_secret, size_t shared_secret_len) +{ + struct pair_cipher_context *cctx; + enum pair_keys write_key; + enum pair_keys read_key; + int ret; + + // Note that events is opposite, probably because it is a reverse connection + switch (channel) + { + case 0: + write_key = PAIR_CONTROL_WRITE; + read_key = PAIR_CONTROL_READ; + break; + case 1: + write_key = PAIR_EVENTS_READ; + read_key = PAIR_EVENTS_WRITE; + break; + case 2: + write_key = PAIR_CONTROL_READ; + read_key = PAIR_CONTROL_WRITE; + break; + case 3: + write_key = PAIR_EVENTS_WRITE; + read_key = PAIR_EVENTS_READ; + break; + default: + return NULL; + } + + cctx = calloc(1, sizeof(struct pair_cipher_context)); + if (!cctx) + goto error; + + cctx->type = type; + + ret = hkdf_extract_expand(cctx->encryption_key, sizeof(cctx->encryption_key), shared_secret, shared_secret_len, write_key); + if (ret < 0) + goto error; + + ret = hkdf_extract_expand(cctx->decryption_key, sizeof(cctx->decryption_key), shared_secret, shared_secret_len, read_key); + if (ret < 0) + goto error; + + return cctx; + + error: + pair_cipher_free(cctx); + return NULL; +} + +static int +pair_encrypt(uint8_t **ciphertext, size_t *ciphertext_len, uint8_t *plaintext, size_t plaintext_len, struct pair_cipher_context *cctx) +{ + uint8_t nonce[NONCE_LENGTH] = { 0 }; + uint8_t tag[AUTHTAG_LENGTH]; + uint8_t *plain_block; + uint8_t *cipher_block; + uint16_t block_len; + int nblocks; + int ret; + int i; + + if (plaintext_len == 0 || !plaintext) + return -1; + + // Encryption is done in blocks, where each block consists of a short, the + // encrypted data and an auth tag. The short is the size of the encrypted + // data. The encrypted data in the block cannot exceed ENCRYPTED_LEN_MAX. + nblocks = 1 + ((plaintext_len - 1) / ENCRYPTED_LEN_MAX); // Ceiling of division + + *ciphertext_len = nblocks * (sizeof(block_len) + AUTHTAG_LENGTH) + plaintext_len; + *ciphertext = malloc(*ciphertext_len); + + for (i = 0, plain_block = plaintext, cipher_block = *ciphertext; i < nblocks; i++) + { + // If it is the last block we will encrypt only the remaining data + block_len = (i + 1 == nblocks) ? (plaintext + plaintext_len - plain_block) : ENCRYPTED_LEN_MAX; + + memcpy(nonce + 4, &(cctx->encryption_counter), sizeof(cctx->encryption_counter));// TODO BE or LE? + + // Write the ciphered block + memcpy(cipher_block, &block_len, sizeof(block_len)); // TODO BE or LE? + ret = encrypt_chacha(cipher_block + sizeof(block_len), plain_block, block_len, cctx->encryption_key, sizeof(cctx->encryption_key), &block_len, sizeof(block_len), tag, sizeof(tag), nonce); + if (ret < 0) + { + cctx->errmsg = "Encryption with chacha poly1305 failed"; + free(*ciphertext); + return -1; + } + memcpy(cipher_block + sizeof(block_len) + block_len, tag, AUTHTAG_LENGTH); + + plain_block += block_len; + cipher_block += block_len + sizeof(block_len) + AUTHTAG_LENGTH; + cctx->encryption_counter++; + } + + assert(plain_block == plaintext + plaintext_len); + assert(cipher_block == *ciphertext + *ciphertext_len); + +#ifdef DEBUG_PAIR + hexdump("Encrypted:\n", *ciphertext, *ciphertext_len); +#endif + + return 0; +} + +static int +pair_decrypt(uint8_t **plaintext, size_t *plaintext_len, uint8_t *ciphertext, size_t ciphertext_len, struct pair_cipher_context *cctx) +{ + uint8_t nonce[NONCE_LENGTH] = { 0 }; + uint8_t tag[AUTHTAG_LENGTH]; + uint8_t *plain_block; + uint8_t *cipher_block; + uint16_t block_len; + int ret; + + if (ciphertext_len < sizeof(block_len) || !ciphertext) + return -1; + + // This will allocate more than we need. Since we don't know the number of + // blocks in the ciphertext yet we can't calculate the exact required length. + *plaintext = malloc(ciphertext_len); + + for (plain_block = *plaintext, cipher_block = ciphertext; cipher_block < ciphertext + ciphertext_len; ) + { + memcpy(&block_len, cipher_block, sizeof(block_len)); // TODO BE or LE? + if (cipher_block + block_len + sizeof(block_len) + AUTHTAG_LENGTH > ciphertext + ciphertext_len) + { + cctx->errmsg = "Corrupt block length in encrypted data"; + free(*plaintext); + return -1; // Corrupt block_len, stop before we read over the end + } + + memcpy(tag, cipher_block + sizeof(block_len) + block_len, sizeof(tag)); + memcpy(nonce + 4, &(cctx->decryption_counter), sizeof(cctx->decryption_counter));// TODO BE or LE? + + ret = decrypt_chacha(plain_block, cipher_block + sizeof(block_len), block_len, cctx->decryption_key, sizeof(cctx->decryption_key), &block_len, sizeof(block_len), tag, sizeof(tag), nonce); + if (ret < 0) + { + cctx->errmsg = "Decryption with chacha poly1305 failed"; + free(*plaintext); + return -1; + } + + plain_block += block_len; + cipher_block += block_len + sizeof(block_len) + AUTHTAG_LENGTH; + cctx->decryption_counter++; + } + + assert(plain_block < *plaintext + ciphertext_len); + assert(cipher_block == ciphertext + ciphertext_len); + + *plaintext_len = plain_block - *plaintext; + +#ifdef DEBUG_PAIR + hexdump("Decrypted:\n", *plaintext, *plaintext_len); +#endif + + return 0; +} + +const struct pair_definition pair_homekit_normal = +{ + .pair_setup_new = pair_setup_new, + .pair_setup_free = pair_setup_free, + .pair_setup_result = pair_setup_result, + + .pair_setup_request1 = pair_setup_request1, + .pair_setup_request2 = pair_setup_request2, + .pair_setup_request3 = pair_setup_request3, + + .pair_setup_response1 = pair_setup_response1, + .pair_setup_response2 = pair_setup_response2, + .pair_setup_response3 = pair_setup_response3, + + .pair_verify_request1 = pair_verify_request1, + .pair_verify_request2 = pair_verify_request2, + + .pair_verify_response1 = pair_verify_response1, + .pair_verify_response2 = pair_verify_response2, + + .pair_cipher_new = pair_cipher_new, + .pair_cipher_free = pair_cipher_free, + + .pair_encrypt = pair_encrypt, + .pair_decrypt = pair_decrypt, +}; + +const struct pair_definition pair_homekit_transient = pair_homekit_normal; diff --git a/src/outputs/raop.c b/src/outputs/raop.c index cf516308..abce2c20 100644 --- a/src/outputs/raop.c +++ b/src/outputs/raop.c @@ -66,7 +66,7 @@ #include "outputs.h" #ifdef RAOP_VERIFICATION -#include "raop_verification.h" +#include "pair.h" #endif #define ALAC_HEADER_LEN 3 @@ -223,9 +223,9 @@ struct raop_session unsigned short timing_port; // ATV4 has this set to 0, but it is not used by forked-daapd anyway #ifdef RAOP_VERIFICATION - /* Device verification, see raop_verification.h */ - struct verification_verify_context *verification_verify_ctx; - struct verification_setup_context *verification_setup_ctx; + /* Device verification, see pair.h */ + struct pair_verify_context *pair_verify_ctx; + struct pair_setup_context *pair_setup_ctx; #endif int server_fd; @@ -3989,7 +3989,7 @@ raop_cb_startup_options(struct evrtsp_request *req, void *arg) #ifdef RAOP_VERIFICATION static int -raop_verification_response_process(int step, struct evrtsp_request *req, struct raop_session *rs) +raop_pair_response_process(int step, struct evrtsp_request *req, struct raop_session *rs) { uint8_t *response; const char *errmsg; @@ -4016,20 +4016,20 @@ raop_verification_response_process(int step, struct evrtsp_request *req, struct switch (step) { case 1: - ret = verification_setup_response1(rs->verification_setup_ctx, response, len); - errmsg = verification_setup_errmsg(rs->verification_setup_ctx); + ret = pair_setup_response1(rs->pair_setup_ctx, response, len); + errmsg = pair_setup_errmsg(rs->pair_setup_ctx); break; case 2: - ret = verification_setup_response2(rs->verification_setup_ctx, response, len); - errmsg = verification_setup_errmsg(rs->verification_setup_ctx); + ret = pair_setup_response2(rs->pair_setup_ctx, response, len); + errmsg = pair_setup_errmsg(rs->pair_setup_ctx); break; case 3: - ret = verification_setup_response3(rs->verification_setup_ctx, response, len); - errmsg = verification_setup_errmsg(rs->verification_setup_ctx); + ret = pair_setup_response3(rs->pair_setup_ctx, response, len); + errmsg = pair_setup_errmsg(rs->pair_setup_ctx); break; case 4: - ret = verification_verify_response1(rs->verification_verify_ctx, response, len); - errmsg = verification_verify_errmsg(rs->verification_verify_ctx); + ret = pair_verify_response1(rs->pair_verify_ctx, response, len); + errmsg = pair_verify_errmsg(rs->pair_verify_ctx); break; case 5: ret = 0; @@ -4046,7 +4046,7 @@ raop_verification_response_process(int step, struct evrtsp_request *req, struct } static int -raop_verification_request_send(int step, struct raop_session *rs, void (*cb)(struct evrtsp_request *, void *)) +raop_pair_request_send(int step, struct raop_session *rs, void (*cb)(struct evrtsp_request *, void *)) { struct evrtsp_request *req; uint8_t *body; @@ -4059,32 +4059,32 @@ raop_verification_request_send(int step, struct raop_session *rs, void (*cb)(str switch (step) { case 1: - body = verification_setup_request1(&len, rs->verification_setup_ctx); - errmsg = verification_setup_errmsg(rs->verification_setup_ctx); + body = pair_setup_request1(&len, rs->pair_setup_ctx); + errmsg = pair_setup_errmsg(rs->pair_setup_ctx); url = "/pair-setup-pin"; ctype = "application/x-apple-binary-plist"; break; case 2: - body = verification_setup_request2(&len, rs->verification_setup_ctx); - errmsg = verification_setup_errmsg(rs->verification_setup_ctx); + body = pair_setup_request2(&len, rs->pair_setup_ctx); + errmsg = pair_setup_errmsg(rs->pair_setup_ctx); url = "/pair-setup-pin"; ctype = "application/x-apple-binary-plist"; break; case 3: - body = verification_setup_request3(&len, rs->verification_setup_ctx); - errmsg = verification_setup_errmsg(rs->verification_setup_ctx); + body = pair_setup_request3(&len, rs->pair_setup_ctx); + errmsg = pair_setup_errmsg(rs->pair_setup_ctx); url = "/pair-setup-pin"; ctype = "application/x-apple-binary-plist"; break; case 4: - body = verification_verify_request1(&len, rs->verification_verify_ctx); - errmsg = verification_verify_errmsg(rs->verification_verify_ctx); + body = pair_verify_request1(&len, rs->pair_verify_ctx); + errmsg = pair_verify_errmsg(rs->pair_verify_ctx); url = "/pair-verify"; ctype = "application/octet-stream"; break; case 5: - body = verification_verify_request2(&len, rs->verification_verify_ctx); - errmsg = verification_verify_errmsg(rs->verification_verify_ctx); + body = pair_verify_request2(&len, rs->pair_verify_ctx); + errmsg = pair_verify_errmsg(rs->pair_verify_ctx); url = "/pair-verify"; ctype = "application/octet-stream"; break; @@ -4135,15 +4135,15 @@ raop_verification_request_send(int step, struct raop_session *rs, void (*cb)(str } static void -raop_cb_verification_verify_step2(struct evrtsp_request *req, void *arg) +raop_cb_pair_verify_step2(struct evrtsp_request *req, void *arg) { struct raop_session *rs = arg; struct output_device *device; int ret; - verification_verify_free(rs->verification_verify_ctx); + pair_verify_free(rs->pair_verify_ctx); - ret = raop_verification_response_process(5, req, rs); + ret = raop_pair_response_process(5, req, rs); if (ret < 0) { device = outputs_device_get(rs->device_id); @@ -4170,13 +4170,13 @@ raop_cb_verification_verify_step2(struct evrtsp_request *req, void *arg) } static void -raop_cb_verification_verify_step1(struct evrtsp_request *req, void *arg) +raop_cb_pair_verify_step1(struct evrtsp_request *req, void *arg) { struct raop_session *rs = arg; struct output_device *device; int ret; - ret = raop_verification_response_process(4, req, rs); + ret = raop_pair_response_process(4, req, rs); if (ret < 0) { device = outputs_device_get(rs->device_id); @@ -4189,61 +4189,68 @@ raop_cb_verification_verify_step1(struct evrtsp_request *req, void *arg) goto error; } - ret = raop_verification_request_send(5, rs, raop_cb_verification_verify_step2); + ret = raop_pair_request_send(5, rs, raop_cb_pair_verify_step2); if (ret < 0) goto error; return; error: - verification_verify_free(rs->verification_verify_ctx); - rs->verification_verify_ctx = NULL; + pair_verify_free(rs->pair_verify_ctx); + rs->pair_verify_ctx = NULL; rs->state = RAOP_STATE_PASSWORD; session_failure(rs); } static int -raop_verification_verify(struct raop_session *rs) +raop_pair_verify(struct raop_session *rs) { struct output_device *device; + const char *auth_key; int ret; device = outputs_device_get(rs->device_id); if (!device) goto error; - CHECK_NULL(L_RAOP, rs->verification_verify_ctx = verification_verify_new(device->auth_key)); + // Backwards compat - older versions saved the key as concat of public key and + // private key, but the pair_verify_new only wants the private key + if (strlen(device->auth_key) == (32 + 64) * 2) + auth_key = device->auth_key + 32; + else + auth_key = device->auth_key; - ret = raop_verification_request_send(4, rs, raop_cb_verification_verify_step1); + CHECK_NULL(L_RAOP, rs->pair_verify_ctx = pair_verify_new(PAIR_FRUIT, auth_key, NULL)); + + ret = raop_pair_request_send(4, rs, raop_cb_pair_verify_step1); if (ret < 0) goto error; return 0; error: - verification_verify_free(rs->verification_verify_ctx); - rs->verification_verify_ctx = NULL; + pair_verify_free(rs->pair_verify_ctx); + rs->pair_verify_ctx = NULL; return -1; } - static void -raop_cb_verification_setup_step3(struct evrtsp_request *req, void *arg) +raop_cb_pair_setup_step3(struct evrtsp_request *req, void *arg) { struct raop_session *rs = arg; struct output_device *device; const char *authorization_key; int ret; - ret = raop_verification_response_process(3, req, rs); + ret = raop_pair_response_process(3, req, rs); if (ret < 0) goto out; - ret = verification_setup_result(&authorization_key, rs->verification_setup_ctx); + ret = pair_setup_result(&authorization_key, NULL, NULL, rs->pair_setup_ctx); if (ret < 0) { - DPRINTF(E_LOG, L_RAOP, "Verification setup result error: %s\n", verification_setup_errmsg(rs->verification_setup_ctx)); + DPRINTF(E_LOG, L_RAOP, "Verification setup result error: %s\n", pair_setup_errmsg(rs->pair_setup_ctx)); goto out; } @@ -4263,8 +4270,8 @@ raop_cb_verification_setup_step3(struct evrtsp_request *req, void *arg) rs->state = RAOP_STATE_STOPPED; out: - verification_setup_free(rs->verification_setup_ctx); - rs->verification_setup_ctx = NULL; + pair_setup_free(rs->pair_setup_ctx); + rs->pair_setup_ctx = NULL; // Callback to player with result raop_status(rs); @@ -4275,62 +4282,62 @@ raop_cb_verification_setup_step3(struct evrtsp_request *req, void *arg) } static void -raop_cb_verification_setup_step2(struct evrtsp_request *req, void *arg) +raop_cb_pair_setup_step2(struct evrtsp_request *req, void *arg) { struct raop_session *rs = arg; int ret; - ret = raop_verification_response_process(2, req, rs); + ret = raop_pair_response_process(2, req, rs); if (ret < 0) goto error; - ret = raop_verification_request_send(3, rs, raop_cb_verification_setup_step3); + ret = raop_pair_request_send(3, rs, raop_cb_pair_setup_step3); if (ret < 0) goto error; return; error: - verification_setup_free(rs->verification_setup_ctx); - rs->verification_setup_ctx = NULL; + pair_setup_free(rs->pair_setup_ctx); + rs->pair_setup_ctx = NULL; session_failure(rs); } static void -raop_cb_verification_setup_step1(struct evrtsp_request *req, void *arg) +raop_cb_pair_setup_step1(struct evrtsp_request *req, void *arg) { struct raop_session *rs = arg; int ret; - ret = raop_verification_response_process(1, req, rs); + ret = raop_pair_response_process(1, req, rs); if (ret < 0) goto error; - ret = raop_verification_request_send(2, rs, raop_cb_verification_setup_step2); + ret = raop_pair_request_send(2, rs, raop_cb_pair_setup_step2); if (ret < 0) goto error; return; error: - verification_setup_free(rs->verification_setup_ctx); - rs->verification_setup_ctx = NULL; + pair_setup_free(rs->pair_setup_ctx); + rs->pair_setup_ctx = NULL; session_failure(rs); } static int -raop_verification_setup(struct raop_session *rs, const char *pin) +raop_pair_setup(struct raop_session *rs, const char *pin) { int ret; - rs->verification_setup_ctx = verification_setup_new(pin); - if (!rs->verification_setup_ctx) + rs->pair_setup_ctx = pair_setup_new(PAIR_FRUIT, pin, NULL); + if (!rs->pair_setup_ctx) { DPRINTF(E_LOG, L_RAOP, "Out of memory for verification setup context\n"); return -1; } - ret = raop_verification_request_send(1, rs, raop_cb_verification_setup_step1); + ret = raop_pair_request_send(1, rs, raop_cb_pair_setup_step1); if (ret < 0) goto error; @@ -4339,8 +4346,8 @@ raop_verification_setup(struct raop_session *rs, const char *pin) return 0; error: - verification_setup_free(rs->verification_setup_ctx); - rs->verification_setup_ctx = NULL; + pair_setup_free(rs->pair_setup_ctx); + rs->pair_setup_ctx = NULL; return -1; } @@ -4355,7 +4362,7 @@ raop_device_authorize(struct output_device *device, const char *pin, int callbac if (!rs) return -1; - ret = raop_verification_setup(rs, pin); + ret = raop_pair_setup(rs, pin); if (ret < 0) { DPRINTF(E_LOG, L_RAOP, "Could not send verification setup request to '%s' (address %s)\n", device->name, rs->address); @@ -4368,7 +4375,7 @@ raop_device_authorize(struct output_device *device, const char *pin, int callbac #else static int -raop_verification_verify(struct raop_session *rs) +raop_pair_verify(struct raop_session *rs) { DPRINTF(E_LOG, L_RAOP, "Device '%s' requires verification, but forked-daapd was built with --disable-verification\n", rs->devname); @@ -4702,7 +4709,7 @@ raop_device_start_generic(struct output_device *device, int callback_id, bool on return -1; if (device->auth_key) - ret = raop_verification_verify(rs); + ret = raop_pair_verify(rs); else if (device->requires_auth) ret = raop_send_req_pin_start(rs, raop_cb_pin_start, "device_start"); else diff --git a/src/outputs/raop_verification.h b/src/outputs/raop_verification.h deleted file mode 100644 index e175a087..00000000 --- a/src/outputs/raop_verification.h +++ /dev/null @@ -1,66 +0,0 @@ -#ifndef __VERIFICATION_H__ -#define __VERIFICATION_H__ - -#include - -struct verification_setup_context; -struct verification_verify_context; - -/* When you have the pin-code (must be 4 bytes), create a new context with this - * function and then call verification_setup_request1() - */ -struct verification_setup_context * -verification_setup_new(const char *pin); -void -verification_setup_free(struct verification_setup_context *sctx); - -/* Returns last error message - */ -const char * -verification_setup_errmsg(struct verification_setup_context *sctx); - -uint8_t * -verification_setup_request1(uint32_t *len, struct verification_setup_context *sctx); -uint8_t * -verification_setup_request2(uint32_t *len, struct verification_setup_context *sctx); -uint8_t * -verification_setup_request3(uint32_t *len, struct verification_setup_context *sctx); - -int -verification_setup_response1(struct verification_setup_context *sctx, const uint8_t *data, uint32_t data_len); -int -verification_setup_response2(struct verification_setup_context *sctx, const uint8_t *data, uint32_t data_len); -int -verification_setup_response3(struct verification_setup_context *sctx, const uint8_t *data, uint32_t data_len); - -/* Returns a 0-terminated string that is the authorisation key. The caller - * should save it and use it later to initialize verification_verify_new(). - * Note that the pointer becomes invalid when you free sctx. - */ -int -verification_setup_result(const char **authorisation_key, struct verification_setup_context *sctx); - - -/* When you have completed the setup you can extract a key with - * verification_setup_result(). Give the string as input to this function to - * create a verification context and then call verification_verify_request1() - */ -struct verification_verify_context * -verification_verify_new(const char *authorisation_key); -void -verification_verify_free(struct verification_verify_context *vctx); - -/* Returns last error message - */ -const char * -verification_verify_errmsg(struct verification_verify_context *vctx); - -uint8_t * -verification_verify_request1(uint32_t *len, struct verification_verify_context *vctx); -uint8_t * -verification_verify_request2(uint32_t *len, struct verification_verify_context *vctx); - -int -verification_verify_response1(struct verification_verify_context *vctx, const uint8_t *data, uint32_t data_len); - -#endif /* !__VERIFICATION_H__ */