mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2026-05-31 21:38:19 -04:00
module-raop: Add transient HomeKit pairing
This commit is contained in:
parent
75eb403d15
commit
45a0460722
2 changed files with 432 additions and 37 deletions
|
|
@ -64,7 +64,7 @@
|
||||||
* #raop.domain = ""
|
* #raop.domain = ""
|
||||||
* #raop.device = ""
|
* #raop.device = ""
|
||||||
* #raop.transport = "udp" | "tcp"
|
* #raop.transport = "udp" | "tcp"
|
||||||
* #raop.encryption.type = "RSA" | "auth_setup" | "none"
|
* #raop.encryption.type = "RSA" | "auth_setup" | "pair_setup" | "none"
|
||||||
* #raop.audio.codec = "PCM" | "ALAC" | "AAC" | "AAC-ELD"
|
* #raop.audio.codec = "PCM" | "ALAC" | "AAC" | "AAC-ELD"
|
||||||
* #audio.channels = 2
|
* #audio.channels = 2
|
||||||
* #audio.format = "S16" | "S24" | "S32"
|
* #audio.format = "S16" | "S24" | "S32"
|
||||||
|
|
@ -105,12 +105,14 @@ PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME);
|
||||||
|
|
||||||
static const struct spa_dict_item module_props[] = {
|
static const struct spa_dict_item module_props[] = {
|
||||||
{ PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" },
|
{ PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" },
|
||||||
{ PW_KEY_MODULE_DESCRIPTION, "Discover remote streams" },
|
{ PW_KEY_MODULE_DESCRIPTION, "Discover remote speakers" },
|
||||||
{ PW_KEY_MODULE_USAGE, MODULE_USAGE },
|
{ PW_KEY_MODULE_USAGE, MODULE_USAGE },
|
||||||
{ PW_KEY_MODULE_VERSION, PACKAGE_VERSION },
|
{ PW_KEY_MODULE_VERSION, PACKAGE_VERSION },
|
||||||
};
|
};
|
||||||
|
|
||||||
#define SERVICE_TYPE_SINK "_raop._tcp"
|
#define SERVICE_TYPE_RAOP "_raop._tcp"
|
||||||
|
#define SERVICE_TYPE_AP "_airplay._tcp"
|
||||||
|
|
||||||
|
|
||||||
struct impl {
|
struct impl {
|
||||||
struct pw_context *context;
|
struct pw_context *context;
|
||||||
|
|
@ -122,7 +124,8 @@ struct impl {
|
||||||
|
|
||||||
AvahiPoll *avahi_poll;
|
AvahiPoll *avahi_poll;
|
||||||
AvahiClient *client;
|
AvahiClient *client;
|
||||||
AvahiServiceBrowser *sink_browser;
|
AvahiServiceBrowser *sink_browser_raop;
|
||||||
|
AvahiServiceBrowser *sink_browser_ap;
|
||||||
|
|
||||||
struct spa_list tunnel_list;
|
struct spa_list tunnel_list;
|
||||||
};
|
};
|
||||||
|
|
@ -182,8 +185,10 @@ static void impl_free(struct impl *impl)
|
||||||
spa_list_consume(t, &impl->tunnel_list, link)
|
spa_list_consume(t, &impl->tunnel_list, link)
|
||||||
free_tunnel(t);
|
free_tunnel(t);
|
||||||
|
|
||||||
if (impl->sink_browser)
|
if (impl->sink_browser_raop)
|
||||||
avahi_service_browser_free(impl->sink_browser);
|
avahi_service_browser_free(impl->sink_browser_raop);
|
||||||
|
if (impl->sink_browser_ap)
|
||||||
|
avahi_service_browser_free(impl->sink_browser_ap);
|
||||||
if (impl->client)
|
if (impl->client)
|
||||||
avahi_client_free(impl->client);
|
avahi_client_free(impl->client);
|
||||||
if (impl->avahi_poll)
|
if (impl->avahi_poll)
|
||||||
|
|
@ -215,7 +220,30 @@ static bool str_in_list(const char *haystack, const char *delimiters, const char
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void pw_properties_from_avahi_string(const char *key, const char *value,
|
static void pw_properties_from_ap_txt(const char *key, const char *value,
|
||||||
|
struct pw_properties *props)
|
||||||
|
{
|
||||||
|
if (spa_streq(key, "deviceid")) {
|
||||||
|
pw_properties_set(props, "raop.device", value);
|
||||||
|
}
|
||||||
|
else if (spa_streq(key, "features")) {
|
||||||
|
pw_properties_set(props, "raop.features", value);
|
||||||
|
}
|
||||||
|
else if (spa_streq(key, "flags")) {
|
||||||
|
pw_properties_set(props, "raop.flags", value);
|
||||||
|
}
|
||||||
|
else if (spa_streq(key, "pk")) {
|
||||||
|
pw_properties_set(props, "raop.pk", value);
|
||||||
|
}
|
||||||
|
else if (spa_streq(key, "pi")) {
|
||||||
|
pw_properties_set(props, "raop.pi", value);
|
||||||
|
}
|
||||||
|
else if (spa_streq(key, "psi")) {
|
||||||
|
pw_properties_set(props, "raop.psi", value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void pw_properties_from_raop_txt(const char *key, const char *value,
|
||||||
struct pw_properties *props)
|
struct pw_properties *props)
|
||||||
{
|
{
|
||||||
if (spa_streq(key, "device")) {
|
if (spa_streq(key, "device")) {
|
||||||
|
|
@ -393,6 +421,37 @@ static void resolver_cb(AvahiServiceResolver *r, AvahiIfIndex interface, AvahiPr
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pw_log_info("mdns service type: %s", type);
|
||||||
|
if (strcmp(type, SERVICE_TYPE_AP) == 0) {
|
||||||
|
for (l = txt; l; l = l->next) {
|
||||||
|
char *key, *value;
|
||||||
|
|
||||||
|
if (avahi_string_list_get_pair(l, &key, &value, NULL) != 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
pw_properties_from_ap_txt(key, value, props);
|
||||||
|
avahi_free(key);
|
||||||
|
avahi_free(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO lorbus
|
||||||
|
// if !feature audio goto done
|
||||||
|
pw_properties_set(props, "raop.encryption.type", "pair_setup");
|
||||||
|
pw_properties_set(props, "raop.transport", "udp");
|
||||||
|
|
||||||
|
} else {
|
||||||
|
for (l = txt; l; l = l->next) {
|
||||||
|
char *key, *value;
|
||||||
|
|
||||||
|
if (avahi_string_list_get_pair(l, &key, &value, NULL) != 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
pw_properties_from_raop_txt(key, value, props);
|
||||||
|
avahi_free(key);
|
||||||
|
avahi_free(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
avahi_address_snprint(at, sizeof(at), a);
|
avahi_address_snprint(at, sizeof(at), a);
|
||||||
ipv = protocol == AVAHI_PROTO_INET ? 4 : 6;
|
ipv = protocol == AVAHI_PROTO_INET ? 4 : 6;
|
||||||
pw_properties_setf(props, "raop.ip", "%s", at);
|
pw_properties_setf(props, "raop.ip", "%s", at);
|
||||||
|
|
@ -402,17 +461,6 @@ static void resolver_cb(AvahiServiceResolver *r, AvahiIfIndex interface, AvahiPr
|
||||||
pw_properties_setf(props, "raop.hostname", "%s", host_name);
|
pw_properties_setf(props, "raop.hostname", "%s", host_name);
|
||||||
pw_properties_setf(props, "raop.domain", "%s", domain);
|
pw_properties_setf(props, "raop.domain", "%s", domain);
|
||||||
|
|
||||||
for (l = txt; l; l = l->next) {
|
|
||||||
char *key, *value;
|
|
||||||
|
|
||||||
if (avahi_string_list_get_pair(l, &key, &value, NULL) != 0)
|
|
||||||
break;
|
|
||||||
|
|
||||||
pw_properties_from_avahi_string(key, value, props);
|
|
||||||
avahi_free(key);
|
|
||||||
avahi_free(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((str = pw_properties_get(impl->properties, "raop.latency.ms")) != NULL)
|
if ((str = pw_properties_get(impl->properties, "raop.latency.ms")) != NULL)
|
||||||
pw_properties_set(props, "raop.latency.ms", str);
|
pw_properties_set(props, "raop.latency.ms", str);
|
||||||
|
|
||||||
|
|
@ -502,9 +550,13 @@ static void client_callback(AvahiClient *c, AvahiClientState state, void *userda
|
||||||
case AVAHI_CLIENT_S_REGISTERING:
|
case AVAHI_CLIENT_S_REGISTERING:
|
||||||
case AVAHI_CLIENT_S_RUNNING:
|
case AVAHI_CLIENT_S_RUNNING:
|
||||||
case AVAHI_CLIENT_S_COLLISION:
|
case AVAHI_CLIENT_S_COLLISION:
|
||||||
if (impl->sink_browser == NULL)
|
if (impl->sink_browser_raop == NULL)
|
||||||
impl->sink_browser = make_browser(impl, SERVICE_TYPE_SINK);
|
impl->sink_browser_raop = make_browser(impl, SERVICE_TYPE_RAOP);
|
||||||
if (impl->sink_browser == NULL)
|
if (impl->sink_browser_raop == NULL)
|
||||||
|
goto error;
|
||||||
|
if (impl->sink_browser_ap == NULL)
|
||||||
|
impl->sink_browser_ap = make_browser(impl, SERVICE_TYPE_AP);
|
||||||
|
if (impl->sink_browser_ap == NULL)
|
||||||
goto error;
|
goto error;
|
||||||
break;
|
break;
|
||||||
case AVAHI_CLIENT_FAILURE:
|
case AVAHI_CLIENT_FAILURE:
|
||||||
|
|
@ -513,9 +565,13 @@ static void client_callback(AvahiClient *c, AvahiClientState state, void *userda
|
||||||
|
|
||||||
SPA_FALLTHROUGH;
|
SPA_FALLTHROUGH;
|
||||||
case AVAHI_CLIENT_CONNECTING:
|
case AVAHI_CLIENT_CONNECTING:
|
||||||
if (impl->sink_browser) {
|
if (impl->sink_browser_raop) {
|
||||||
avahi_service_browser_free(impl->sink_browser);
|
avahi_service_browser_free(impl->sink_browser_raop);
|
||||||
impl->sink_browser = NULL;
|
impl->sink_browser_raop = NULL;
|
||||||
|
}
|
||||||
|
if (impl->sink_browser_ap) {
|
||||||
|
avahi_service_browser_free(impl->sink_browser_ap);
|
||||||
|
impl->sink_browser_ap = NULL;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@
|
||||||
/* SPDX-FileCopyrightText: Copyright © 2021 Wim Taymans */
|
/* SPDX-FileCopyrightText: Copyright © 2021 Wim Taymans */
|
||||||
/* SPDX-License-Identifier: MIT */
|
/* SPDX-License-Identifier: MIT */
|
||||||
|
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <assert.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
|
|
@ -26,6 +28,7 @@
|
||||||
#include <openssl/engine.h>
|
#include <openssl/engine.h>
|
||||||
#include <openssl/aes.h>
|
#include <openssl/aes.h>
|
||||||
#include <openssl/md5.h>
|
#include <openssl/md5.h>
|
||||||
|
#include <openssl/evp.h>
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
|
|
@ -44,6 +47,8 @@
|
||||||
#include <pipewire/i18n.h>
|
#include <pipewire/i18n.h>
|
||||||
|
|
||||||
#include "module-raop/rtsp-client.h"
|
#include "module-raop/rtsp-client.h"
|
||||||
|
#include "module-raop/srp.h"
|
||||||
|
#include "module-raop/tlv.h"
|
||||||
|
|
||||||
/** \page page_module_raop_sink PipeWire Module: AirPlay Sink
|
/** \page page_module_raop_sink PipeWire Module: AirPlay Sink
|
||||||
*
|
*
|
||||||
|
|
@ -136,9 +141,16 @@ PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME);
|
||||||
#define MD5_DIGEST_LENGTH 16
|
#define MD5_DIGEST_LENGTH 16
|
||||||
#endif
|
#endif
|
||||||
#define MD5_HASH_LENGTH (2*MD5_DIGEST_LENGTH)
|
#define MD5_HASH_LENGTH (2*MD5_DIGEST_LENGTH)
|
||||||
|
#ifndef SHA512_DIGEST_LENGTH
|
||||||
|
#define SHA512_DIGEST_LENGTH 64
|
||||||
|
#endif
|
||||||
|
|
||||||
#define DEFAULT_USER_AGENT "iTunes/11.0.4 (Windows; N)"
|
#define DEFAULT_USER_AGENT "iTunes/11.0.4 (Windows; N)"
|
||||||
#define DEFAULT_USER_NAME "iTunes"
|
#define DEFAULT_USER_NAME "iTunes"
|
||||||
|
#define AP_USER_AGENT "AirPlay/381.13"
|
||||||
|
#define AP_USER_NAME "AirPlay"
|
||||||
|
#define AP_REQUEST_BUFSIZE 4096
|
||||||
|
#define AP_SRP_USER_NAME "Pair-Setup"
|
||||||
|
|
||||||
#define MAX_PORT_RETRY 128
|
#define MAX_PORT_RETRY 128
|
||||||
|
|
||||||
|
|
@ -171,7 +183,7 @@ PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME);
|
||||||
|
|
||||||
static const struct spa_dict_item module_props[] = {
|
static const struct spa_dict_item module_props[] = {
|
||||||
{ PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" },
|
{ PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" },
|
||||||
{ PW_KEY_MODULE_DESCRIPTION, "An RAOP audio sink" },
|
{ PW_KEY_MODULE_DESCRIPTION, "An AirPlay audio sink" },
|
||||||
{ PW_KEY_MODULE_USAGE, MODULE_USAGE },
|
{ PW_KEY_MODULE_USAGE, MODULE_USAGE },
|
||||||
{ PW_KEY_MODULE_VERSION, PACKAGE_VERSION },
|
{ PW_KEY_MODULE_VERSION, PACKAGE_VERSION },
|
||||||
};
|
};
|
||||||
|
|
@ -183,7 +195,8 @@ enum {
|
||||||
enum {
|
enum {
|
||||||
CRYPTO_NONE,
|
CRYPTO_NONE,
|
||||||
CRYPTO_RSA,
|
CRYPTO_RSA,
|
||||||
CRYPTO_AUTH_SETUP,
|
CRYPTO_AUTH_SETUP = 4,
|
||||||
|
CRYPTO_PAIR_TRANSIENT = 8,
|
||||||
};
|
};
|
||||||
enum {
|
enum {
|
||||||
CODEC_PCM,
|
CODEC_PCM,
|
||||||
|
|
@ -232,6 +245,22 @@ struct impl {
|
||||||
uint8_t aes_iv[AES_CHUNK_SIZE]; /* Initialization vector for cbc */
|
uint8_t aes_iv[AES_CHUNK_SIZE]; /* Initialization vector for cbc */
|
||||||
EVP_CIPHER_CTX *ctx;
|
EVP_CIPHER_CTX *ctx;
|
||||||
|
|
||||||
|
struct SRPUser *srp_user;
|
||||||
|
uint8_t public_key[32];
|
||||||
|
uint8_t private_key[64];
|
||||||
|
const uint8_t *pkA;
|
||||||
|
int pkA_len;
|
||||||
|
uint8_t *pkB;
|
||||||
|
uint64_t pkB_len;
|
||||||
|
const uint8_t *M1;
|
||||||
|
int M1_len;
|
||||||
|
uint8_t *M2;
|
||||||
|
int M2_len;
|
||||||
|
uint8_t *salt;
|
||||||
|
uint64_t salt_len;
|
||||||
|
uint8_t shared_secret[64];
|
||||||
|
size_t shared_secret_len; // Will be 32 (normal) or 64 (transient)
|
||||||
|
|
||||||
uint16_t control_port;
|
uint16_t control_port;
|
||||||
int control_fd;
|
int control_fd;
|
||||||
struct spa_source *control_source;
|
struct spa_source *control_source;
|
||||||
|
|
@ -1384,6 +1413,290 @@ static int rtsp_do_raop_auth(struct impl *impl, const struct spa_dict *headers)
|
||||||
return rtsp_send(impl, "OPTIONS", NULL, NULL, rtsp_raop_auth_reply);
|
return rtsp_send(impl, "OPTIONS", NULL, NULL, rtsp_raop_auth_reply);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static tlv_values_t * tlv_message_process(const uint8_t *data, size_t data_len)
|
||||||
|
{
|
||||||
|
tlv_values_t *response;
|
||||||
|
tlv_t *error;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
pw_log_debug("tlv: processing tlv message");
|
||||||
|
|
||||||
|
response = tlv_new();
|
||||||
|
if (!response) {
|
||||||
|
pw_log_error("tlv: Out of memory");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = tlv_parse(data, data_len, response);
|
||||||
|
if (ret < 0) {
|
||||||
|
pw_log_error("tlv: Could not parse TLV");
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (tlv_t *t=response->head; t; t=t->next) {
|
||||||
|
pw_log_info("tlv: parsed type %d value (%zu bytes)", t->type, t->size);
|
||||||
|
}
|
||||||
|
|
||||||
|
error = tlv_get_value(response, AP2_TLVType_Error);
|
||||||
|
if (error) {
|
||||||
|
if (error->value[0] == AP2_TLVError_Authentication)
|
||||||
|
pw_log_error("tlv: Device returned an authentication failure");
|
||||||
|
else if (error->value[0] == AP2_TLVError_Backoff)
|
||||||
|
pw_log_error("tlv: Device told us to back off pairing attempts");
|
||||||
|
else if (error->value[0] == AP2_TLVError_MaxPeers)
|
||||||
|
pw_log_error("tlv: Max peers trying to connect to device");
|
||||||
|
else if (error->value[0] == AP2_TLVError_MaxTries)
|
||||||
|
pw_log_error("tlv: Max pairing attempts reached");
|
||||||
|
else if (error->value[0] == AP2_TLVError_Unavailable)
|
||||||
|
pw_log_error("tlv: Device is unavailable at this time");
|
||||||
|
else
|
||||||
|
pw_log_error("tlv: Device is busy/returned unknown error");
|
||||||
|
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
|
||||||
|
error:
|
||||||
|
tlv_free(response);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int rtsp_ap2_pair_setup2_reply(void *data, int status, const struct spa_dict *headers, const struct pw_array *content)
|
||||||
|
{
|
||||||
|
struct impl *impl = data;
|
||||||
|
tlv_values_t *response;
|
||||||
|
tlv_t *proof;
|
||||||
|
const uint8_t *session_key;
|
||||||
|
int session_key_len;
|
||||||
|
uint32_t content_length;
|
||||||
|
const char *str;
|
||||||
|
|
||||||
|
pw_log_info("pair-setup (req 2) status: %d", status);
|
||||||
|
|
||||||
|
if ((str = spa_dict_lookup(headers, "Content-Length")) == NULL) {
|
||||||
|
pw_log_error("pair-setup (req 2) res: Missing Content-Length");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!spa_atou32(str, &content_length, 0)) {
|
||||||
|
pw_log_error("pair-setup (req 2) res: Could not read Content-Length");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
pw_log_debug("pair-setup (req 2) res: Content-Length: %s", str);
|
||||||
|
|
||||||
|
response = tlv_message_process((const uint8_t *)content->data, (size_t)content_length);
|
||||||
|
if (!response) {
|
||||||
|
pw_log_error("pair-setup (req 2) res: Received an error");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
proof = tlv_get_value(response, AP2_TLVType_Proof);
|
||||||
|
if (!proof || proof->size != SHA512_DIGEST_LENGTH) {
|
||||||
|
pw_log_error("pair-setup (req 2) res: Missing or invalid proof");
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl->M2_len = proof->size;
|
||||||
|
impl->M2 = malloc(impl->M2_len);
|
||||||
|
memcpy(impl->M2, proof->value, impl->M2_len);
|
||||||
|
|
||||||
|
// Check M2
|
||||||
|
srp_user_verify_session(impl->srp_user, (const unsigned char *)impl->M2);
|
||||||
|
if (!srp_user_is_authenticated(impl->srp_user)) {
|
||||||
|
pw_log_error("pair-setup (req 2) res: Server authentication failed");
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
session_key = srp_user_get_session_key(impl->srp_user, &session_key_len);
|
||||||
|
if (!session_key) {
|
||||||
|
pw_log_error("pair-setup (req 2) res: Could not compute session key");
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sizeof(impl->shared_secret) < (unsigned long int) session_key_len) {
|
||||||
|
pw_log_error("pair-setup (req 2) res: invalid session key");
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(impl->shared_secret, session_key, session_key_len);
|
||||||
|
impl->shared_secret_len = session_key_len;
|
||||||
|
|
||||||
|
pw_log_info("received shared secret");
|
||||||
|
|
||||||
|
tlv_free(response);
|
||||||
|
// TODO
|
||||||
|
pw_properties_set(impl->headers, "X-Apple-HKP", NULL);
|
||||||
|
|
||||||
|
//return rtsp_do_setup(impl);
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
error:
|
||||||
|
tlv_free(response);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int rtsp_do_ap2_pair_setup2(struct impl *impl)
|
||||||
|
{
|
||||||
|
tlv_values_t *request;
|
||||||
|
uint8_t *data;
|
||||||
|
size_t data_len = AP_REQUEST_BUFSIZE;
|
||||||
|
const char *auth_username = NULL;
|
||||||
|
int ret;
|
||||||
|
// TLV Pairing Message State
|
||||||
|
uint8_t state = 0x03;
|
||||||
|
|
||||||
|
data = malloc(data_len);
|
||||||
|
request = tlv_new();
|
||||||
|
|
||||||
|
// Calculate A
|
||||||
|
srp_user_start_authentication(impl->srp_user, &auth_username, &impl->pkA, &impl->pkA_len);
|
||||||
|
|
||||||
|
// Calculate M1 (client proof)
|
||||||
|
srp_user_process_challenge(impl->srp_user, impl->salt, impl->salt_len, impl->pkB, impl->pkB_len, &impl->M1, &impl->M1_len);
|
||||||
|
|
||||||
|
tlv_add_value(request, AP2_TLVType_State, &state, sizeof(state));
|
||||||
|
tlv_add_value(request, AP2_TLVType_PublicKey, impl->pkA, impl->pkA_len);
|
||||||
|
tlv_add_value(request, AP2_TLVType_Proof, impl->M1, impl->M1_len);
|
||||||
|
|
||||||
|
ret = tlv_format(request, data, &data_len);
|
||||||
|
if (ret < 0) {
|
||||||
|
pw_log_error("pair-setup 2: tlv_format returned an error");
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (tlv_t *t=request->head; t; t=t->next) {
|
||||||
|
pw_log_info("pair-setup 2: sending TLV type %d (bytes: %zu)", t->type, t->size);
|
||||||
|
}
|
||||||
|
|
||||||
|
tlv_free(request);
|
||||||
|
|
||||||
|
return pw_rtsp_client_url_send(impl->rtsp, "/pair-setup", "POST", &impl->headers->dict,
|
||||||
|
"application/octet-stream", data, data_len,
|
||||||
|
rtsp_ap2_pair_setup2_reply, impl);
|
||||||
|
|
||||||
|
error:
|
||||||
|
tlv_free(request);
|
||||||
|
free(data);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int rtsp_ap2_pair_setup1_reply(void *data, int status, const struct spa_dict *headers, const struct pw_array *content)
|
||||||
|
{
|
||||||
|
struct impl *impl = data;
|
||||||
|
uint32_t content_length;
|
||||||
|
const char *len_str;
|
||||||
|
tlv_values_t *response;
|
||||||
|
tlv_t *pk;
|
||||||
|
tlv_t *salt;
|
||||||
|
|
||||||
|
pw_log_info("pair-setup (req 1) status: %d", status);
|
||||||
|
|
||||||
|
if ((len_str = spa_dict_lookup(headers, "Content-Length")) == NULL) {
|
||||||
|
pw_log_error("pair-setup (req 1) res: Missing Content-Length header");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!spa_atou32(len_str, &content_length, 0)) {
|
||||||
|
pw_log_error("pair-setup (req 1) res: Could not read Content-Length header");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
pw_log_info("pair-setup (req 1) content len: %d", content_length);
|
||||||
|
|
||||||
|
response = tlv_message_process((const uint8_t *)content->data, (size_t)content_length);
|
||||||
|
if (!response) {
|
||||||
|
pw_log_error("pair-setup (req 1) res: message process error");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
pk = tlv_get_value(response, AP2_TLVType_PublicKey);
|
||||||
|
if (!pk) {
|
||||||
|
pw_log_error("pair-setup (req 1) res: missing public key");
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
if (pk->size > (long unsigned int) get_N_len(SRP_NG_3072)) {
|
||||||
|
pw_log_error("pair-setup (req 1) res: invalid public key");
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
salt = tlv_get_value(response, AP2_TLVType_Salt);
|
||||||
|
if (!salt) {
|
||||||
|
pw_log_error("pair-setup (req 1) res: missing salt");
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
if (salt->size != 16) {
|
||||||
|
pw_log_error("pair-setup (req 1) res: invalid salt");
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl->pkB_len = pk->size;
|
||||||
|
impl->pkB = malloc(impl->pkB_len);
|
||||||
|
memcpy(impl->pkB, pk->value, impl->pkB_len);
|
||||||
|
|
||||||
|
impl->salt_len = salt->size;
|
||||||
|
impl->salt = malloc(impl->salt_len);
|
||||||
|
memcpy(impl->salt, salt->value, impl->salt_len);
|
||||||
|
|
||||||
|
tlv_free(response);
|
||||||
|
|
||||||
|
return rtsp_do_ap2_pair_setup2(impl);
|
||||||
|
|
||||||
|
error:
|
||||||
|
tlv_free(response);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int rtsp_do_ap2_pair_setup1(struct impl *impl)
|
||||||
|
{
|
||||||
|
tlv_values_t *request;
|
||||||
|
uint8_t *data;
|
||||||
|
size_t data_len;
|
||||||
|
// TLV Pairing Method: Pair Setup
|
||||||
|
uint8_t method = 0x00;
|
||||||
|
// TLV Pairing Flags: Transient
|
||||||
|
uint8_t flags = 0x10;
|
||||||
|
// TLV Pairing Message State
|
||||||
|
uint8_t state = 0x01;
|
||||||
|
char *pin = "3939";
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
data_len = AP_REQUEST_BUFSIZE;
|
||||||
|
data = malloc(data_len);
|
||||||
|
request = tlv_new();
|
||||||
|
|
||||||
|
impl->srp_user = srp_user_new(SRP_SHA512, SRP_NG_3072, AP_SRP_USER_NAME, (unsigned char *)pin, strlen(pin), 0, 0);
|
||||||
|
if (!impl->srp_user) {
|
||||||
|
pw_log_error("pair-setup 1: SRP user creation failed");
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
tlv_add_value(request, AP2_TLVType_State, &state, sizeof(state));
|
||||||
|
tlv_add_value(request, AP2_TLVType_Method, &method, sizeof(method));
|
||||||
|
tlv_add_value(request, AP2_TLVType_Flags, &flags, sizeof(flags));
|
||||||
|
|
||||||
|
ret = tlv_format(request, data, &data_len);
|
||||||
|
if (ret < 0) {
|
||||||
|
pw_log_error("pair-setup 1: tlv_format returned an error");
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (tlv_t *t=request->head; t; t=t->next) {
|
||||||
|
pw_log_info("pair-setup 1: sending TLV type %d value (%zu bytes)", t->type, t->size);
|
||||||
|
}
|
||||||
|
|
||||||
|
tlv_free(request);
|
||||||
|
|
||||||
|
pw_properties_set(impl->headers, "X-Apple-HKP", "4");
|
||||||
|
|
||||||
|
return pw_rtsp_client_url_send(impl->rtsp, "/pair-setup", "POST", &impl->headers->dict,
|
||||||
|
"application/octet-stream", (unsigned char *)data, data_len,
|
||||||
|
rtsp_ap2_pair_setup1_reply, impl);
|
||||||
|
error:
|
||||||
|
tlv_free(request);
|
||||||
|
free(data);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
static int rtsp_raop_options_reply(void *data, int status, const struct spa_dict *headers, const struct pw_array *content)
|
static int rtsp_raop_options_reply(void *data, int status, const struct spa_dict *headers, const struct pw_array *content)
|
||||||
{
|
{
|
||||||
struct impl *impl = data;
|
struct impl *impl = data;
|
||||||
|
|
@ -1409,30 +1722,51 @@ static void rtsp_connected(void *data)
|
||||||
{
|
{
|
||||||
struct impl *impl = data;
|
struct impl *impl = data;
|
||||||
uint32_t sci[2];
|
uint32_t sci[2];
|
||||||
uint8_t rac[16];
|
|
||||||
char sac[16*4];
|
|
||||||
int res;
|
|
||||||
|
|
||||||
pw_log_info("connected");
|
pw_log_info("connected");
|
||||||
|
|
||||||
impl->connected = true;
|
impl->connected = true;
|
||||||
|
|
||||||
if ((res = pw_getrandom(sci, sizeof(sci), 0)) < 0 ||
|
// TODO: Do a GET /info first
|
||||||
(res = pw_getrandom(rac, sizeof(rac), 0)) < 0) {
|
|
||||||
pw_log_error("error generating random data: %s", spa_strerror(res));
|
if (pw_getrandom(sci, sizeof(sci), 0) < 0) {
|
||||||
|
pw_log_error("error generating random data: %m");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
pw_properties_setf(impl->headers, "Client-Instance",
|
pw_properties_setf(impl->headers, "Client-Instance",
|
||||||
"%08x%08x", sci[0], sci[1]);
|
"%08X%08X", sci[0], sci[1]);
|
||||||
|
|
||||||
base64_encode(rac, sizeof(rac), sac, '\0');
|
pw_properties_setf(impl->headers, "DACP-ID",
|
||||||
pw_properties_set(impl->headers, "Apple-Challenge", sac);
|
"%08X%08X", sci[0], sci[1]);
|
||||||
|
|
||||||
pw_properties_set(impl->headers, "User-Agent", DEFAULT_USER_AGENT);
|
switch (impl->encryption) {
|
||||||
|
case CRYPTO_PAIR_TRANSIENT:
|
||||||
|
pw_properties_set(impl->headers, "User-Agent", AP_USER_AGENT);
|
||||||
|
|
||||||
pw_rtsp_client_send(impl->rtsp, "OPTIONS", &impl->headers->dict,
|
rtsp_do_ap2_pair_setup1(impl);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
uint8_t rac[16];
|
||||||
|
char sac[16*4];
|
||||||
|
|
||||||
|
if (pw_getrandom(rac, sizeof(rac), 0) < 0) {
|
||||||
|
pw_log_error("error generating random data: %m");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
base64_encode(rac, sizeof(rac), sac, '\0');
|
||||||
|
pw_properties_set(impl->headers, "Apple-Challenge", sac);
|
||||||
|
|
||||||
|
pw_properties_set(impl->headers, "User-Agent", DEFAULT_USER_AGENT);
|
||||||
|
|
||||||
|
pw_rtsp_client_send(impl->rtsp, "OPTIONS", &impl->headers->dict,
|
||||||
NULL, NULL, rtsp_raop_options_reply, impl);
|
NULL, NULL, rtsp_raop_options_reply, impl);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void connection_cleanup(struct impl *impl)
|
static void connection_cleanup(struct impl *impl)
|
||||||
|
|
@ -1888,6 +2222,9 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args)
|
||||||
pw_log_error( "can't create cipher context: %m");
|
pw_log_error( "can't create cipher context: %m");
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl->srp_user = NULL;
|
||||||
|
|
||||||
if (args == NULL)
|
if (args == NULL)
|
||||||
args = "";
|
args = "";
|
||||||
|
|
||||||
|
|
@ -1992,6 +2329,8 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args)
|
||||||
impl->encryption = CRYPTO_RSA;
|
impl->encryption = CRYPTO_RSA;
|
||||||
else if (spa_streq(str, "auth_setup"))
|
else if (spa_streq(str, "auth_setup"))
|
||||||
impl->encryption = CRYPTO_AUTH_SETUP;
|
impl->encryption = CRYPTO_AUTH_SETUP;
|
||||||
|
else if (spa_streq(str, "pair_setup"))
|
||||||
|
impl->encryption = CRYPTO_PAIR_TRANSIENT;
|
||||||
else {
|
else {
|
||||||
pw_log_error( "can't handle encryption type %s", str);
|
pw_log_error( "can't handle encryption type %s", str);
|
||||||
res = -EINVAL;
|
res = -EINVAL;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue