add infrastructure for pair-pin-start, + changing features

This commit is contained in:
F. Duncanh
2023-11-23 20:26:21 -05:00
parent fea2893540
commit 44365fe59c
9 changed files with 178 additions and 45 deletions

View File

@@ -25,6 +25,7 @@
#include <openssl/evp.h>
#include <openssl/err.h>
#include <openssl/rand.h>
#include <assert.h>
#include <stdlib.h>
@@ -263,7 +264,6 @@ void x25519_derive_secret(unsigned char secret[X25519_KEY_SIZE], const x25519_ke
// ED25519
struct ed25519_key_s {
EVP_PKEY *pkey;
unsigned char ed_secret[ED25519_KEY_SIZE];
@@ -497,3 +497,7 @@ void sha_destroy(sha_ctx_t *ctx) {
free(ctx);
}
}
int get_random_bytes(unsigned char *buf, int num) {
return RAND_bytes(buf, num);
}

View File

@@ -145,15 +145,22 @@ struct dnssd_s {
char *hw_addr;
int hw_addr_len;
uint32_t features1;
uint32_t features2;
unsigned char require_pw;
};
dnssd_t *
dnssd_init(const char* name, int name_len, const char* hw_addr, int hw_addr_len, int *error)
dnssd_init(const char* name, int name_len, const char* hw_addr, int hw_addr_len, int *error, int require_pw)
{
dnssd_t *dnssd;
char *end;
unsigned long features;
if (error) *error = DNSSD_ERROR_NOERROR;
dnssd = calloc(1, sizeof(dnssd_t));
@@ -162,6 +169,24 @@ dnssd_init(const char* name, int name_len, const char* hw_addr, int hw_addr_len,
return NULL;
}
dnssd->require_pw = (unsigned char) require_pw;
features = strtoul(FEATURES_1, &end, 16);
if (!end || (features & 0xFFFFFFFF) != features) {
free (dnssd);
if (error) *error = DNSSD_ERROR_BADFEATURES;
return NULL;
}
dnssd->features1 = (uint32_t) features;
features = strtoul(FEATURES_2, &end, 16);
if (!end || (features & 0xFFFFFFFF) != features) {
free (dnssd);
if (error) *error = DNSSD_ERROR_BADFEATURES;
return NULL;
}
dnssd->features2 = (uint32_t) features;
#ifdef WIN32
dnssd->module = LoadLibraryA("dnssd.dll");
if (!dnssd->module) {
@@ -259,20 +284,27 @@ dnssd_register_raop(dnssd_t *dnssd, unsigned short port)
{
char servname[MAX_SERVNAME];
DNSServiceErrorType retval;
char features[22];
assert(dnssd);
snprintf(features, sizeof(features), "0x%X,0x%X", dnssd->features1, dnssd->features2);
dnssd->TXTRecordCreate(&dnssd->raop_record, 0, NULL);
dnssd->TXTRecordSetValue(&dnssd->raop_record, "ch", strlen(RAOP_CH), RAOP_CH);
dnssd->TXTRecordSetValue(&dnssd->raop_record, "cn", strlen(RAOP_CN), RAOP_CN);
dnssd->TXTRecordSetValue(&dnssd->raop_record, "da", strlen(RAOP_DA), RAOP_DA);
dnssd->TXTRecordSetValue(&dnssd->raop_record, "et", strlen(RAOP_ET), RAOP_ET);
dnssd->TXTRecordSetValue(&dnssd->raop_record, "vv", strlen(RAOP_VV), RAOP_VV);
dnssd->TXTRecordSetValue(&dnssd->raop_record, "ft", strlen(RAOP_FT), RAOP_FT);
dnssd->TXTRecordSetValue(&dnssd->raop_record, "ft", strlen(features), features);
dnssd->TXTRecordSetValue(&dnssd->raop_record, "am", strlen(GLOBAL_MODEL), GLOBAL_MODEL);
dnssd->TXTRecordSetValue(&dnssd->raop_record, "md", strlen(RAOP_MD), RAOP_MD);
dnssd->TXTRecordSetValue(&dnssd->raop_record, "rhd", strlen(RAOP_RHD), RAOP_RHD);
dnssd->TXTRecordSetValue(&dnssd->raop_record, "pw", strlen("false"), "false");
if (dnssd->require_pw) {
dnssd->TXTRecordSetValue(&dnssd->raop_record, "pw", strlen("true"), "true");
} else {
dnssd->TXTRecordSetValue(&dnssd->raop_record, "pw", strlen("false"), "false");
}
dnssd->TXTRecordSetValue(&dnssd->raop_record, "sr", strlen(RAOP_SR), RAOP_SR);
dnssd->TXTRecordSetValue(&dnssd->raop_record, "ss", strlen(RAOP_SS), RAOP_SS);
dnssd->TXTRecordSetValue(&dnssd->raop_record, "sv", strlen(RAOP_SV), RAOP_SV);
@@ -281,7 +313,7 @@ dnssd_register_raop(dnssd_t *dnssd, unsigned short port)
dnssd->TXTRecordSetValue(&dnssd->raop_record, "sf", strlen(RAOP_SF), RAOP_SF);
dnssd->TXTRecordSetValue(&dnssd->raop_record, "vs", strlen(RAOP_VS), RAOP_VS);
dnssd->TXTRecordSetValue(&dnssd->raop_record, "vn", strlen(RAOP_VN), RAOP_VN);
dnssd->TXTRecordSetValue(&dnssd->raop_record, "pk", strlen(RAOP_PK), RAOP_PK);
dnssd->TXTRecordSetValue(&dnssd->raop_record, "pk", strlen(PK), PK);
/* Convert hardware address to string */
if (utils_hwaddr_raop(servname, sizeof(servname), dnssd->hw_addr, dnssd->hw_addr_len) < 0) {
@@ -315,9 +347,12 @@ dnssd_register_airplay(dnssd_t *dnssd, unsigned short port)
{
char device_id[3 * MAX_HWADDR_LEN];
DNSServiceErrorType retval;
char features[22];
assert(dnssd);
snprintf(features, sizeof(features), "0x%X,0x%X", dnssd->features1, dnssd->features2);
/* Convert hardware address to string */
if (utils_hwaddr_airplay(device_id, sizeof(device_id), dnssd->hw_addr, dnssd->hw_addr_len) < 0) {
/* FIXME: handle better */
@@ -327,10 +362,15 @@ dnssd_register_airplay(dnssd_t *dnssd, unsigned short port)
dnssd->TXTRecordCreate(&dnssd->airplay_record, 0, NULL);
dnssd->TXTRecordSetValue(&dnssd->airplay_record, "deviceid", strlen(device_id), device_id);
dnssd->TXTRecordSetValue(&dnssd->airplay_record, "features", strlen(AIRPLAY_FEATURES), AIRPLAY_FEATURES);
dnssd->TXTRecordSetValue(&dnssd->airplay_record, "features", strlen(features), features);
dnssd->TXTRecordSetValue(&dnssd->airplay_record, "flags", strlen(AIRPLAY_FLAGS), AIRPLAY_FLAGS);
dnssd->TXTRecordSetValue(&dnssd->airplay_record, "model", strlen(GLOBAL_MODEL), GLOBAL_MODEL);
dnssd->TXTRecordSetValue(&dnssd->airplay_record, "pk", strlen(AIRPLAY_PK), AIRPLAY_PK);
dnssd->TXTRecordSetValue(&dnssd->airplay_record, "pk", strlen(PK), PK);
if (dnssd->require_pw) {
dnssd->TXTRecordSetValue(&dnssd->airplay_record, "pw", strlen("true"), "true");
} else {
dnssd->TXTRecordSetValue(&dnssd->airplay_record, "pw", strlen("false"), "false");
}
dnssd->TXTRecordSetValue(&dnssd->airplay_record, "pi", strlen(AIRPLAY_PI), AIRPLAY_PI);
dnssd->TXTRecordSetValue(&dnssd->airplay_record, "srcvers", strlen(AIRPLAY_SRCVERS), AIRPLAY_SRCVERS);
dnssd->TXTRecordSetValue(&dnssd->airplay_record, "vv", strlen(AIRPLAY_VV), AIRPLAY_VV);
@@ -409,3 +449,28 @@ dnssd_unregister_airplay(dnssd_t *dnssd)
free(dnssd->hw_addr);
}
}
uint64_t dnssd_get_airplay_features(dnssd_t *dnssd) {
uint64_t features = ((uint64_t) dnssd->features2) << 32;
features += (uint64_t) dnssd->features1;
return features;
}
void dnssd_set_airplay_features(dnssd_t *dnssd, int bit, int val) {
uint32_t mask;
uint32_t *features;
if (bit < 0 || bit > 63) return;
if (val < 0 || val > 1) return;
if (bit >= 32) {
mask = 0x1 << (bit - 32);
features = &(dnssd->features2);
} else {
mask = 0x1 << bit;
features = &(dnssd->features1);
}
if (val) {
*features = *features | mask;
} else {
*features = *features & ~mask;
}
}

View File

@@ -14,6 +14,7 @@
#ifndef DNSSD_H
#define DNSSD_H
#include <stdint.h>
#if defined(WIN32) && defined(DLL_EXPORT)
# define DNSSD_API __declspec(dllexport)
@@ -30,10 +31,11 @@ extern "C" {
#define DNSSD_ERROR_OUTOFMEM 2
#define DNSSD_ERROR_LIBNOTFOUND 3
#define DNSSD_ERROR_PROCNOTFOUND 4
#define DNSSD_ERROR_BADFEATURES 5
typedef struct dnssd_s dnssd_t;
DNSSD_API dnssd_t *dnssd_init(const char *name, int name_len, const char *hw_addr, int hw_addr_len, int *error);
DNSSD_API dnssd_t *dnssd_init(const char *name, int name_len, const char *hw_addr, int hw_addr_len, int *error, int require_pw);
DNSSD_API int dnssd_register_raop(dnssd_t *dnssd, unsigned short port);
DNSSD_API int dnssd_register_airplay(dnssd_t *dnssd, unsigned short port);
@@ -44,7 +46,9 @@ DNSSD_API void dnssd_unregister_airplay(dnssd_t *dnssd);
DNSSD_API const char *dnssd_get_airplay_txt(dnssd_t *dnssd, int *length);
DNSSD_API const char *dnssd_get_name(dnssd_t *dnssd, int *length);
DNSSD_API const char *dnssd_get_hw_addr(dnssd_t *dnssd, int *length);
DNSSD_API void dnssd_set_airplay_features(dnssd_t *dnssd, int bit, int val);
DNSSD_API uint64_t dnssd_get_airplay_features(dnssd_t *dnssd);
DNSSD_API void dnssd_destroy(dnssd_t *dnssd);
#ifdef __cplusplus

View File

@@ -19,15 +19,17 @@
#define DNSSDINT_H
#include "global.h"
#define PK "b07727d6f6cd6e08b58ede525ec3cdeaa252ad9f683feb212ef8a205246554e7"
#define RAOP_TXTVERS "1"
#define RAOP_CH "2" /* Audio channels: 2 */
#define RAOP_CN "0,1,2,3" /* Audio codec: PCM, ALAC, AAC, AAC ELD */
#define RAOP_ET "0,3,5" /* Encryption type: None, FairPlay, FairPlay SAPv2.5 */
#define RAOP_VV "2"
//#define FEATURES_1 "0x5A7FFEE6" /* first 32 bits of features, with bit 27 ("supports legacy pairing") ON */
#define FEATURES_1 "0x527FFEE6" /* first 32 bits of features, with bit 27 ("supports legacy pairing") OFF */
#define FEATURES_2 "0x0" /* second 32 bits of features */
#define RAOP_FT FEATURES_1 "," FEATURES_2
#define FEATURES_1 "0x5A7FFEE6" /* first 32 bits of features, with bit 27 ("supports legacy pairing") ON */
//#define FEATURES_1 "0x527FFEE6" /* first 32 bits of features, with bit 27 ("supports legacy pairing") OFF */
#define FEATURES_2 "0x0" /* second 32 bits of features */
#define RAOP_RHD "5.6.0.0"
#define RAOP_SF "0x4"
#define RAOP_SV "false"
@@ -38,16 +40,10 @@
#define RAOP_TP "UDP" /* Transport protocol. Possible values: UDP or TCP or TCP,UDP */
#define RAOP_MD "0,1,2" /* Metadata: text, artwork, progress */
#define RAOP_VN "65537"
#define RAOP_PK "b07727d6f6cd6e08b58ede525ec3cdeaa252ad9f683feb212ef8a205246554e7"
/* use same features for RAOP and AIRPLAY: is this correct? */
#define AIRPLAY_FEATURES_1 FEATURES_1
#define AIRPLAY_FEATURES_2 FEATURES_2
#define AIRPLAY_FEATURES AIRPLAY_FEATURES_1 "," AIRPLAY_FEATURES_2
#define AIRPLAY_SRCVERS GLOBAL_VERSION /*defined in global.h */
#define AIRPLAY_FLAGS "0x4"
#define AIRPLAY_VV "2"
#define AIRPLAY_PK "b07727d6f6cd6e08b58ede525ec3cdeaa252ad9f683feb212ef8a205246554e7"
#define AIRPLAY_PI "2e388006-13ba-4041-9a67-25dd4a43d536"
#endif

View File

@@ -269,3 +269,23 @@ pairing_destroy(pairing_t *pairing)
free(pairing);
}
}
int
random_pin() {
unsigned char random_bytes[2] = { 0 };
unsigned short random_short = 0;
int ret;
/* create a random unsigned short in range 1-9999 */
while (!random_short) {
if ((ret = get_random_bytes(random_bytes, sizeof(random_bytes)) < 1)) {
return -1;
}
memcpy(&random_short, random_bytes, sizeof(random_bytes));
random_short = random_short % 10000;
}
return (int) random_short;
}

View File

@@ -32,6 +32,7 @@ int pairing_session_check_handshake_status(pairing_session_t *session);
int pairing_session_handshake(pairing_session_t *session, const unsigned char ecdh_key[X25519_KEY_SIZE],
const unsigned char ed_key[ED25519_KEY_SIZE]);
int pairing_session_get_public_key(pairing_session_t *session, unsigned char ecdh_key[X25519_KEY_SIZE]);
int random_pin();
int pairing_session_get_signature(pairing_session_t *session, unsigned char signature[PAIRING_SIG_SIZE]);
int pairing_session_finish(pairing_session_t *session, const unsigned char signature[PAIRING_SIG_SIZE]);
void pairing_session_destroy(pairing_session_t *session);

View File

@@ -65,6 +65,9 @@ struct raop_s {
int audio_delay_micros;
int max_ntp_timeouts;
/* for temporary storage of pin during pair-pin start */
unsigned short pin;
};
struct raop_conn_s {
@@ -215,11 +218,9 @@ conn_request(void *ptr, http_request_t *request, http_response_t **response) {
if (!strcmp(method, "GET") && !strcmp(url, "/info")) {
handler = &raop_handler_info;
} else if (!strcmp(method, "POST") && !strcmp(url, "/pair-pin-start")) {
logger_log(conn->raop->logger, LOGGER_ERR, "*** ERROR: Unsupported client request %s with URL %s", method, url);
logger_log(conn->raop->logger, LOGGER_INFO, "*** AirPlay client has requested PIN as implemented on AppleTV,");
logger_log(conn->raop->logger, LOGGER_INFO, "*** but this implementation does not require a PIN and cannot supply one.");
logger_log(conn->raop->logger, LOGGER_INFO, "*** This client behavior may have been required by mobile device management (MDM)");
logger_log(conn->raop->logger, LOGGER_INFO, "*** (such as Apple Configurator or a third-party MDM tool).");
handler = &raop_handler_pairpinstart;
} else if (!strcmp(method, "POST") && !strcmp(url, "/pair-setup-pin")) {
handler = &raop_handler_pairsetup_pin;
} else if (!strcmp(method, "POST") && !strcmp(url, "/pair-setup")) {
handler = &raop_handler_pairsetup;
} else if (!strcmp(method, "POST") && !strcmp(url, "/pair-verify")) {
@@ -422,6 +423,8 @@ raop_init(int max_clients, raop_callbacks_t *callbacks) {
/* Initialize the logger */
raop->logger = logger_init();
/* create a new public key for pairing */
pairing = pairing_init_generate();
if (!pairing) {
free(raop);
@@ -461,6 +464,9 @@ raop_init(int max_clients, raop_callbacks_t *callbacks) {
raop->maxFPS = 30;
raop->overscanned = 0;
/* initialise stored pin */
raop->pin = 0;
/* initialize switch for display of client's streaming data records */
raop->clientFPSdata = 0;

View File

@@ -50,16 +50,14 @@ raop_handler_info(raop_conn_t *conn,
utils_hwaddr_airplay(hw_addr, 3 * hw_addr_raw_len, hw_addr_raw, hw_addr_raw_len);
int pk_len = 0;
char *pk = utils_parse_hex(AIRPLAY_PK, strlen(AIRPLAY_PK), &pk_len);
uint64_t features = ((uint64_t) strtoul(AIRPLAY_FEATURES_2, NULL, 16)) << 32;
features += (uint64_t) strtoul(AIRPLAY_FEATURES_1, NULL, 16);
char *pk = utils_parse_hex(PK, strlen(PK), &pk_len);
plist_t r_node = plist_new_dict();
plist_t txt_airplay_node = plist_new_data(airplay_txt, airplay_txt_len);
plist_dict_set_item(r_node, "txtAirPlay", txt_airplay_node);
uint64_t features = dnssd_get_airplay_features(conn->raop->dnssd);
plist_t features_node = plist_new_uint(features);
plist_dict_set_item(r_node, "features", features_node);
@@ -94,44 +92,44 @@ raop_handler_info(raop_conn_t *conn,
plist_t status_flags_node = plist_new_uint(68);
plist_dict_set_item(r_node, "statusFlags", status_flags_node);
plist_t keep_alive_low_power_node = plist_new_uint(1);
plist_dict_set_item(r_node, "keepAliveLowPower", keep_alive_low_power_node);
plist_t source_version_node = plist_new_string(GLOBAL_VERSION);
plist_dict_set_item(r_node, "sourceVersion", source_version_node);
plist_t pk_node = plist_new_data(pk, pk_len);
plist_dict_set_item(r_node, "pk", pk_node);
plist_t keep_alive_send_stats_as_body_node = plist_new_uint(1);
plist_dict_set_item(r_node, "keepAliveSendStatsAsBody", keep_alive_send_stats_as_body_node);
plist_t device_id_node = plist_new_string(hw_addr);
plist_dict_set_item(r_node, "deviceID", device_id_node);
plist_t audio_latencies_node = plist_new_array();
plist_t audio_latencies_0_node = plist_new_dict();
plist_t audio_latencies_0_audio_type_node = plist_new_string("default");
plist_t audio_latencies_0_input_latency_micros_node = plist_new_uint(0);
plist_t audio_latencies_0_output_latency_micros_node = plist_new_uint(0);
plist_t audio_latencies_0_output_latency_micros_node = plist_new_bool(0);
plist_t audio_latencies_0_type_node = plist_new_uint(100);
plist_dict_set_item(audio_latencies_0_node, "audioType", audio_latencies_0_audio_type_node);
plist_dict_set_item(audio_latencies_0_node, "inputLatencyMicros", audio_latencies_0_input_latency_micros_node);
plist_t audio_latencies_0_audio_type_node = plist_new_string("default");
plist_t audio_latencies_0_input_latency_micros_node = plist_new_bool(0);
plist_dict_set_item(audio_latencies_0_node, "outputLatencyMicros", audio_latencies_0_output_latency_micros_node);
plist_dict_set_item(audio_latencies_0_node, "type", audio_latencies_0_type_node);
plist_dict_set_item(audio_latencies_0_node, "audioType", audio_latencies_0_audio_type_node);
plist_dict_set_item(audio_latencies_0_node, "inputLatencyMicros", audio_latencies_0_input_latency_micros_node);
plist_array_append_item(audio_latencies_node, audio_latencies_0_node);
plist_t audio_latencies_1_node = plist_new_dict();
plist_t audio_latencies_1_audio_type_node = plist_new_string("default");
plist_t audio_latencies_1_input_latency_micros_node = plist_new_uint(0);
plist_t audio_latencies_1_output_latency_micros_node = plist_new_uint(0);
plist_t audio_latencies_1_output_latency_micros_node = plist_new_bool(0);
plist_t audio_latencies_1_type_node = plist_new_uint(101);
plist_dict_set_item(audio_latencies_1_node, "audioType", audio_latencies_1_audio_type_node);
plist_dict_set_item(audio_latencies_1_node, "inputLatencyMicros", audio_latencies_1_input_latency_micros_node);
plist_t audio_latencies_1_audio_type_node = plist_new_string("default");
plist_t audio_latencies_1_input_latency_micros_node = plist_new_bool(0);
plist_dict_set_item(audio_latencies_1_node, "outputLatencyMicros", audio_latencies_1_output_latency_micros_node);
plist_dict_set_item(audio_latencies_1_node, "type", audio_latencies_1_type_node);
plist_dict_set_item(audio_latencies_1_node, "audioType", audio_latencies_1_audio_type_node);
plist_dict_set_item(audio_latencies_1_node, "inputLatencyMicros", audio_latencies_1_input_latency_micros_node);
plist_array_append_item(audio_latencies_node, audio_latencies_1_node);
plist_dict_set_item(r_node, "audioLatencies", audio_latencies_node);
plist_t keep_alive_low_power_node = plist_new_bool(1);
plist_dict_set_item(r_node, "keepAliveLowPower", keep_alive_low_power_node);
plist_t keep_alive_send_stats_as_body_node = plist_new_bool(1);
plist_dict_set_item(r_node, "keepAliveSendStatsAsBody", keep_alive_send_stats_as_body_node);
plist_t model_node = plist_new_string(GLOBAL_MODEL);
plist_dict_set_item(r_node, "model", model_node);
@@ -152,6 +150,7 @@ raop_handler_info(raop_conn_t *conn,
plist_t displays_0_max_fps_node = plist_new_uint(conn->raop->maxFPS);
plist_t displays_0_overscanned_node = plist_new_bool(conn->raop->overscanned);
plist_t displays_0_features = plist_new_uint(14);
plist_dict_set_item(displays_0_node, "uuid", displays_0_uuid_node);
plist_dict_set_item(displays_0_node, "widthPhysical", displays_0_width_physical_node);
plist_dict_set_item(displays_0_node, "heightPhysical", displays_0_height_physical_node);
@@ -174,6 +173,41 @@ raop_handler_info(raop_conn_t *conn,
free(hw_addr);
}
static void
raop_handler_pairpinstart(raop_conn_t *conn,
http_request_t *request, http_response_t *response,
char **response_data, int *response_datalen) {
logger_log(conn->raop->logger, LOGGER_INFO, "client sent PAIR-PIN-START request");
int pin_4 = random_pin();
conn->raop->pin = (unsigned short) pin_4;
if (pin_4 < 0) {
logger_log(conn->raop->logger, LOGGER_ERR, "Failed to generate random pin");
} else {
char pin[6];
snprintf(pin, 5, "%04u", pin_4);
if (conn->raop->callbacks.display_pin) {
conn->raop->callbacks.display_pin(conn->raop->callbacks.cls, pin);
}
logger_log(conn->raop->logger, LOGGER_INFO, "*** CLIENT MUST NOW ENTER PIN = \"%s\" AS AIRPLAY PASSWORD", pin);
}
*response_data = NULL;
response_datalen = 0;
return;
}
static void
raop_handler_pairsetup_pin(raop_conn_t *conn,
http_request_t *request, http_response_t *response,
char **response_data, int *response_datalen) {
/* does nothing yet */
*response_data = NULL;
response_datalen = 0;
return;
}
static void
raop_handler_pairsetup(raop_conn_t *conn,
http_request_t *request, http_response_t *response,