mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2026-05-30 21:37:53 -04:00
Merge branch 'pw-airplay' into 'master'
Draft: module-raop: Add initial AirPlay v2 support See merge request pipewire/pipewire!1658
This commit is contained in:
commit
dbe69e1f9c
11 changed files with 1685 additions and 92 deletions
|
|
@ -275,6 +275,7 @@ summary({'dbus (Bluetooth, rt, portal, pw-reserve)': dbus_dep.found()}, bool_yn:
|
||||||
cdata.set('HAVE_DBUS', dbus_dep.found())
|
cdata.set('HAVE_DBUS', dbus_dep.found())
|
||||||
sdl_dep = dependency('sdl2', required : get_option('sdl2'))
|
sdl_dep = dependency('sdl2', required : get_option('sdl2'))
|
||||||
summary({'SDL2 (video examples)': sdl_dep.found()}, bool_yn: true, section: 'Misc dependencies')
|
summary({'SDL2 (video examples)': sdl_dep.found()}, bool_yn: true, section: 'Misc dependencies')
|
||||||
|
plist_lib = dependency('libplist-2.0', required: get_option('raop'))
|
||||||
drm_dep = dependency('libdrm', required : false)
|
drm_dep = dependency('libdrm', required : false)
|
||||||
readline_dep = dependency('readline', required : get_option('readline'))
|
readline_dep = dependency('readline', required : get_option('readline'))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -170,6 +170,9 @@ context.modules = [
|
||||||
# Provides factories to make session manager objects.
|
# Provides factories to make session manager objects.
|
||||||
{ name = libpipewire-module-session-manager }
|
{ name = libpipewire-module-session-manager }
|
||||||
|
|
||||||
|
# Use zeroconf to detect and load module-raop-sink
|
||||||
|
{ name = libpipewire-module-raop-discover }
|
||||||
|
|
||||||
# Use libcanberra to play X11 Bell
|
# Use libcanberra to play X11 Bell
|
||||||
{ name = libpipewire-module-x11-bell
|
{ name = libpipewire-module-x11-bell
|
||||||
args = {
|
args = {
|
||||||
|
|
|
||||||
|
|
@ -607,12 +607,14 @@ build_module_raop = openssl_lib.found()
|
||||||
if build_module_raop
|
if build_module_raop
|
||||||
pipewire_module_raop_sink = shared_library('pipewire-module-raop-sink',
|
pipewire_module_raop_sink = shared_library('pipewire-module-raop-sink',
|
||||||
[ 'module-raop-sink.c',
|
[ 'module-raop-sink.c',
|
||||||
'module-raop/rtsp-client.c' ],
|
'module-raop/rtsp-client.c',
|
||||||
|
'module-raop/srp.c',
|
||||||
|
'module-raop/tlv.c' ],
|
||||||
include_directories : [configinc],
|
include_directories : [configinc],
|
||||||
install : true,
|
install : true,
|
||||||
install_dir : modules_install_dir,
|
install_dir : modules_install_dir,
|
||||||
install_rpath: modules_install_dir,
|
install_rpath: modules_install_dir,
|
||||||
dependencies : [mathlib, dl_lib, rt_lib, pipewire_dep, openssl_lib],
|
dependencies : [mathlib, dl_lib, rt_lib, pipewire_dep, openssl_lib, plist_lib],
|
||||||
)
|
)
|
||||||
endif
|
endif
|
||||||
summary({'raop-sink (requires OpenSSL)': build_module_raop}, bool_yn: true, section: 'Optional Modules')
|
summary({'raop-sink (requires OpenSSL)': build_module_raop}, bool_yn: true, section: 'Optional Modules')
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -56,6 +56,9 @@ struct pw_rtsp_client {
|
||||||
unsigned int need_flush:1;
|
unsigned int need_flush:1;
|
||||||
|
|
||||||
enum client_recv_state recv_state;
|
enum client_recv_state recv_state;
|
||||||
|
struct pw_rtsp_cipher_context *cipher_ctx;
|
||||||
|
ssize_t (*decrypt) (uint8_t **plaintext, size_t *plaintext_len, const uint8_t *ciphertext, size_t ciphertext_len, struct pw_rtsp_cipher_context *cctx);
|
||||||
|
ssize_t (*encrypt) (uint8_t **ciphertext, size_t *ciphertext_len, const uint8_t *plaintext, size_t plaintext_len, struct pw_rtsp_cipher_context *cctx);
|
||||||
int status;
|
int status;
|
||||||
char line_buf[1024];
|
char line_buf[1024];
|
||||||
size_t line_pos;
|
size_t line_pos;
|
||||||
|
|
@ -92,12 +95,33 @@ struct pw_rtsp_client *pw_rtsp_client_new(struct pw_loop *main_loop,
|
||||||
client->headers = pw_properties_new(NULL, NULL);
|
client->headers = pw_properties_new(NULL, NULL);
|
||||||
pw_array_init(&client->content, 4096);
|
pw_array_init(&client->content, 4096);
|
||||||
client->recv_state = CLIENT_RECV_NONE;
|
client->recv_state = CLIENT_RECV_NONE;
|
||||||
|
client->cipher_ctx = NULL;
|
||||||
|
client->decrypt = NULL;
|
||||||
|
client->encrypt = NULL;
|
||||||
|
|
||||||
pw_log_info("new client %p", client);
|
pw_log_info("new client %p", client);
|
||||||
|
|
||||||
return client;
|
return client;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void pw_rtsp_client_set_decryption(struct pw_rtsp_client *client,
|
||||||
|
ssize_t (*decrypt) (uint8_t **plaintext, size_t *plaintext_len, const uint8_t *ciphertext, size_t ciphertext_len, struct pw_rtsp_cipher_context *cctx))
|
||||||
|
{
|
||||||
|
client->decrypt = decrypt;
|
||||||
|
}
|
||||||
|
|
||||||
|
void pw_rtsp_client_set_encryption(struct pw_rtsp_client *client,
|
||||||
|
ssize_t (*encrypt) (uint8_t **ciphertext, size_t *ciphertext_len, const uint8_t *plaintext, size_t plaintext_len, struct pw_rtsp_cipher_context *cctx))
|
||||||
|
{
|
||||||
|
client->encrypt = encrypt;
|
||||||
|
}
|
||||||
|
|
||||||
|
void pw_rtsp_client_set_cipher_ctx(struct pw_rtsp_client *client,
|
||||||
|
struct pw_rtsp_cipher_context *cipher_ctx)
|
||||||
|
{
|
||||||
|
client->cipher_ctx = cipher_ctx;
|
||||||
|
}
|
||||||
|
|
||||||
void pw_rtsp_client_destroy(struct pw_rtsp_client *client)
|
void pw_rtsp_client_destroy(struct pw_rtsp_client *client)
|
||||||
{
|
{
|
||||||
pw_log_info("destroy client %p", client);
|
pw_log_info("destroy client %p", client);
|
||||||
|
|
@ -154,6 +178,8 @@ int pw_rtsp_client_get_local_ip(struct pw_rtsp_client *client,
|
||||||
|
|
||||||
static int handle_connect(struct pw_rtsp_client *client, int fd)
|
static int handle_connect(struct pw_rtsp_client *client, int fd)
|
||||||
{
|
{
|
||||||
|
pw_log_info("enter handle connect");
|
||||||
|
|
||||||
int res, ip_version;
|
int res, ip_version;
|
||||||
socklen_t len;
|
socklen_t len;
|
||||||
char local_ip[INET6_ADDRSTRLEN];
|
char local_ip[INET6_ADDRSTRLEN];
|
||||||
|
|
@ -185,9 +211,12 @@ static int handle_connect(struct pw_rtsp_client *client, int fd)
|
||||||
|
|
||||||
client->recv_state = CLIENT_RECV_STATUS;
|
client->recv_state = CLIENT_RECV_STATUS;
|
||||||
pw_properties_clear(client->headers);
|
pw_properties_clear(client->headers);
|
||||||
|
pw_array_reset(&client->content);
|
||||||
client->status = 0;
|
client->status = 0;
|
||||||
client->line_pos = 0;
|
client->line_pos = 0;
|
||||||
client->content_length = 0;
|
client->content_length = 0;
|
||||||
|
client->decrypt = NULL;
|
||||||
|
client->encrypt = NULL;
|
||||||
|
|
||||||
pw_rtsp_client_emit_connected(client);
|
pw_rtsp_client_emit_connected(client);
|
||||||
|
|
||||||
|
|
@ -196,7 +225,7 @@ static int handle_connect(struct pw_rtsp_client *client, int fd)
|
||||||
|
|
||||||
static int read_line(struct pw_rtsp_client *client, char **buf)
|
static int read_line(struct pw_rtsp_client *client, char **buf)
|
||||||
{
|
{
|
||||||
int res;
|
int res, idx = 0;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
uint8_t c;
|
uint8_t c;
|
||||||
|
|
@ -217,15 +246,17 @@ static int read_line(struct pw_rtsp_client *client, char **buf)
|
||||||
client->line_pos = 0;
|
client->line_pos = 0;
|
||||||
if (buf)
|
if (buf)
|
||||||
*buf = client->line_buf;
|
*buf = client->line_buf;
|
||||||
return 1;
|
return idx;
|
||||||
}
|
}
|
||||||
if (c == '\r')
|
if (c == '\r')
|
||||||
continue;
|
continue;
|
||||||
if (client->line_pos < sizeof(client->line_buf) - 1)
|
if (client->line_pos < sizeof(client->line_buf) - 1) {
|
||||||
client->line_buf[client->line_pos++] = c;
|
client->line_buf[client->line_pos++] = c;
|
||||||
|
idx++;
|
||||||
|
}
|
||||||
client->line_buf[client->line_pos] = '\0';
|
client->line_buf[client->line_pos] = '\0';
|
||||||
}
|
}
|
||||||
return 0;
|
return idx;
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct message *find_pending(struct pw_rtsp_client *client, uint32_t cseq)
|
static struct message *find_pending(struct pw_rtsp_client *client, uint32_t cseq)
|
||||||
|
|
@ -243,7 +274,7 @@ static int process_status(struct pw_rtsp_client *client, char *buf)
|
||||||
const char *state = NULL, *s;
|
const char *state = NULL, *s;
|
||||||
size_t len;
|
size_t len;
|
||||||
|
|
||||||
pw_log_info("status: %s", buf);
|
pw_log_info("processing status: %s", buf);
|
||||||
|
|
||||||
s = pw_split_walk(buf, " ", &len, &state);
|
s = pw_split_walk(buf, " ", &len, &state);
|
||||||
if (!spa_strstartswith(s, "RTSP/"))
|
if (!spa_strstartswith(s, "RTSP/"))
|
||||||
|
|
@ -355,6 +386,7 @@ static int process_content(struct pw_rtsp_client *client)
|
||||||
|
|
||||||
spa_assert((size_t) res <= client->content_length);
|
spa_assert((size_t) res <= client->content_length);
|
||||||
client->content_length -= res;
|
client->content_length -= res;
|
||||||
|
pw_log_info("processing content (%ld bytes):\n%s", client->content.size, (char *)client->content.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (client->content_length == 0)
|
if (client->content_length == 0)
|
||||||
|
|
@ -369,11 +401,24 @@ static int process_input(struct pw_rtsp_client *client)
|
||||||
char *buf = NULL;
|
char *buf = NULL;
|
||||||
int res;
|
int res;
|
||||||
|
|
||||||
if ((res = read_line(client, &buf)) <= 0)
|
if ((res = read_line(client, &buf)) < 0) {
|
||||||
|
pw_log_debug("ret 1: %d", res);
|
||||||
return res;
|
return res;
|
||||||
|
}
|
||||||
pw_log_debug("received line: %s", buf);
|
pw_log_debug("received line (%ld bytes): %s", res, buf);
|
||||||
|
if (res > 0 && client->decrypt != NULL && client->cipher_ctx != NULL) {
|
||||||
|
pw_log_debug("decrypting");
|
||||||
|
uint8_t *dec_buf = malloc(1024);
|
||||||
|
size_t dec_len;
|
||||||
|
ssize_t dec;
|
||||||
|
dec = client->decrypt(dec_buf, &dec_len, (const uint8_t *)buf, res, client->cipher_ctx);
|
||||||
|
if (dec < 0) {
|
||||||
|
pw_log_debug("ret 2");
|
||||||
|
return dec;
|
||||||
|
}
|
||||||
|
buf = dec_buf;
|
||||||
|
pw_log_debug("plain line: %s", buf);
|
||||||
|
}
|
||||||
switch (client->recv_state) {
|
switch (client->recv_state) {
|
||||||
case CLIENT_RECV_STATUS:
|
case CLIENT_RECV_STATUS:
|
||||||
return process_status(client, buf);
|
return process_status(client, buf);
|
||||||
|
|
@ -445,14 +490,17 @@ on_source_io(void *data, int fd, uint32_t mask)
|
||||||
int res;
|
int res;
|
||||||
|
|
||||||
if (mask & (SPA_IO_ERR | SPA_IO_HUP)) {
|
if (mask & (SPA_IO_ERR | SPA_IO_HUP)) {
|
||||||
|
pw_log_info("ERR");
|
||||||
res = -EPIPE;
|
res = -EPIPE;
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
if (mask & SPA_IO_IN) {
|
if (mask & SPA_IO_IN) {
|
||||||
|
pw_log_info("IN");
|
||||||
if ((res = process_input(client)) < 0)
|
if ((res = process_input(client)) < 0)
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
if (mask & SPA_IO_OUT || client->need_flush) {
|
if (mask & SPA_IO_OUT || client->need_flush) {
|
||||||
|
pw_log_info("OUT");
|
||||||
if (client->connecting) {
|
if (client->connecting) {
|
||||||
if ((res = handle_connect(client, fd)) < 0)
|
if ((res = handle_connect(client, fd)) < 0)
|
||||||
goto error;
|
goto error;
|
||||||
|
|
@ -552,6 +600,9 @@ int pw_rtsp_client_disconnect(struct pw_rtsp_client *client)
|
||||||
client->url = NULL;
|
client->url = NULL;
|
||||||
free(client->session_id);
|
free(client->session_id);
|
||||||
client->session_id = NULL;
|
client->session_id = NULL;
|
||||||
|
if (client->cipher_ctx != NULL)
|
||||||
|
free(client->cipher_ctx);
|
||||||
|
client->cipher_ctx = NULL;
|
||||||
|
|
||||||
spa_list_consume(msg, &client->messages, link) {
|
spa_list_consume(msg, &client->messages, link) {
|
||||||
spa_list_remove(&msg->link);
|
spa_list_remove(&msg->link);
|
||||||
|
|
@ -598,8 +649,19 @@ int pw_rtsp_client_url_send(struct pw_rtsp_client *client, const char *url,
|
||||||
|
|
||||||
fclose(f);
|
fclose(f);
|
||||||
|
|
||||||
msg->data = SPA_PTROFF(msg, sizeof(*msg), void);
|
if (client->encrypt != NULL && client->cipher_ctx != NULL) {
|
||||||
msg->len = len - sizeof(*msg);
|
uint8_t *enc_buf;
|
||||||
|
size_t enc_len;
|
||||||
|
ssize_t enc = client->encrypt(&enc_buf, &enc_len, SPA_PTROFF(msg, sizeof(*msg), void), len - sizeof(*msg), client->cipher_ctx);
|
||||||
|
if (enc < 0)
|
||||||
|
return enc;
|
||||||
|
msg->data = enc_buf;
|
||||||
|
msg->len = enc_len;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
msg->data = SPA_PTROFF(msg, sizeof(*msg), void);
|
||||||
|
msg->len = len - sizeof(*msg);
|
||||||
|
}
|
||||||
msg->offset = 0;
|
msg->offset = 0;
|
||||||
msg->reply = reply;
|
msg->reply = reply;
|
||||||
msg->user_data = user_data;
|
msg->user_data = user_data;
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,15 @@ struct pw_rtsp_client_events {
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct pw_rtsp_cipher_context {
|
||||||
|
uint8_t encryption_key[32];
|
||||||
|
uint8_t decryption_key[32];
|
||||||
|
uint64_t encryption_counter;
|
||||||
|
uint64_t decryption_counter;
|
||||||
|
uint64_t encryption_counter_prev;
|
||||||
|
uint64_t decryption_counter_prev;
|
||||||
|
};
|
||||||
|
|
||||||
struct pw_rtsp_client * pw_rtsp_client_new(struct pw_loop *main_loop,
|
struct pw_rtsp_client * pw_rtsp_client_new(struct pw_loop *main_loop,
|
||||||
struct pw_properties *props,
|
struct pw_properties *props,
|
||||||
size_t user_data_size);
|
size_t user_data_size);
|
||||||
|
|
@ -64,6 +73,14 @@ int pw_rtsp_client_send(struct pw_rtsp_client *client,
|
||||||
int (*reply) (void *user_data, int status, const struct spa_dict *headers, const struct pw_array *content),
|
int (*reply) (void *user_data, int status, const struct spa_dict *headers, const struct pw_array *content),
|
||||||
void *user_data);
|
void *user_data);
|
||||||
|
|
||||||
|
void pw_rtsp_client_set_decryption(struct pw_rtsp_client *client,
|
||||||
|
ssize_t (*decrypt) (uint8_t **plaintext, size_t *plaintext_len, const uint8_t *ciphertext, size_t ciphertext_len, struct pw_rtsp_cipher_context *cctx));
|
||||||
|
|
||||||
|
void pw_rtsp_client_set_encryption(struct pw_rtsp_client *client,
|
||||||
|
ssize_t (*encrypt) (uint8_t **ciphertext, size_t *ciphertext_len, const uint8_t *plaintext, size_t plaintext_len, struct pw_rtsp_cipher_context *cctx));
|
||||||
|
|
||||||
|
void pw_rtsp_client_set_cipher_ctx(struct pw_rtsp_client *client,
|
||||||
|
struct pw_rtsp_cipher_context *cipher_ctx);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
|
|
|
||||||
437
src/modules/module-raop/srp.c
Normal file
437
src/modules/module-raop/srp.c
Normal file
|
|
@ -0,0 +1,437 @@
|
||||||
|
/* PipeWire */
|
||||||
|
/* SPDX-FileCopyrightText: Copyright © 2013 Tom Cocagne */
|
||||||
|
/* SPDX-License-Identifier: MIT */
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#include <openssl/bn.h>
|
||||||
|
#include <openssl/evp.h>
|
||||||
|
|
||||||
|
#include <pipewire/log.h>
|
||||||
|
|
||||||
|
#include "srp.h"
|
||||||
|
|
||||||
|
#ifndef SHA512_DIGEST_LENGTH
|
||||||
|
#define SHA512_DIGEST_LENGTH 64
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef SHA_DIGEST_LENGTH
|
||||||
|
#define SHA_DIGEST_LENGTH 20
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define SRP_SALT_BYTES 16
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int N_len;
|
||||||
|
BIGNUM *N;
|
||||||
|
BIGNUM *g;
|
||||||
|
} NGConstant;
|
||||||
|
|
||||||
|
struct NGHex {
|
||||||
|
int N_len;
|
||||||
|
const char *n_hex;
|
||||||
|
const char *g_hex;
|
||||||
|
};
|
||||||
|
|
||||||
|
// These constants here were pulled from Appendix A of RFC 5054
|
||||||
|
static struct NGHex Ng_constants[] = {
|
||||||
|
{ /* 2048 */
|
||||||
|
256,
|
||||||
|
"AC6BDB41324A9A9BF166DE5E1389582FAF72B6651987EE07FC3192943DB56050A37329CBB4"
|
||||||
|
"A099ED8193E0757767A13DD52312AB4B03310DCD7F48A9DA04FD50E8083969EDB767B0CF60"
|
||||||
|
"95179A163AB3661A05FBD5FAAAE82918A9962F0B93B855F97993EC975EEAA80D740ADBF4FF"
|
||||||
|
"747359D041D5C33EA71D281E446B14773BCA97B43A23FB801676BD207A436C6481F1D2B907"
|
||||||
|
"8717461A5B9D32E688F87748544523B524B0D57D5EA77A2775D2ECFA032CFBDBF52FB37861"
|
||||||
|
"60279004E57AE6AF874E7303CE53299CCC041C7BC308D82A5698F3A8D0C38271AE35F8E9DB"
|
||||||
|
"FBB694B5C803D89F7AE435DE236D525F54759B65E372FCD68EF20FA7111F9E4AFF73",
|
||||||
|
"2"
|
||||||
|
},
|
||||||
|
{ /* 3072 */
|
||||||
|
384,
|
||||||
|
"FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B"
|
||||||
|
"139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485"
|
||||||
|
"B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1F"
|
||||||
|
"E649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23"
|
||||||
|
"DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32"
|
||||||
|
"905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF69558"
|
||||||
|
"17183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521"
|
||||||
|
"ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D7"
|
||||||
|
"1E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B1817"
|
||||||
|
"7B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82"
|
||||||
|
"D120A93AD2CAFFFFFFFFFFFFFFFF",
|
||||||
|
"5"
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
static NGConstant *new_ng(SRP_NGType ng_type, const char *n_hex, const char *g_hex)
|
||||||
|
{
|
||||||
|
NGConstant *ng = calloc(1, sizeof(NGConstant));
|
||||||
|
|
||||||
|
n_hex = Ng_constants[ng_type].n_hex;
|
||||||
|
g_hex = Ng_constants[ng_type].g_hex;
|
||||||
|
|
||||||
|
BN_hex2bn(&ng->N, n_hex);
|
||||||
|
BN_hex2bn(&ng->g, g_hex);
|
||||||
|
|
||||||
|
ng->N_len = BN_num_bytes(ng->N);
|
||||||
|
|
||||||
|
assert(ng->N_len == Ng_constants[ng_type].N_len);
|
||||||
|
|
||||||
|
return ng;
|
||||||
|
}
|
||||||
|
|
||||||
|
int get_N_len(SRP_NGType ng_type)
|
||||||
|
{
|
||||||
|
return Ng_constants[ng_type].N_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SRPUser {
|
||||||
|
SRP_HashAlgorithm hash_alg;
|
||||||
|
NGConstant *ng;
|
||||||
|
|
||||||
|
BIGNUM *a;
|
||||||
|
BIGNUM *A;
|
||||||
|
BIGNUM *S;
|
||||||
|
|
||||||
|
const unsigned char *bytes_A;
|
||||||
|
int len_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;
|
||||||
|
};
|
||||||
|
|
||||||
|
const EVP_MD *hash_func(SRP_HashAlgorithm alg)
|
||||||
|
{
|
||||||
|
switch (alg) {
|
||||||
|
case SRP_SHA1:
|
||||||
|
return EVP_sha1();
|
||||||
|
case SRP_SHA512:
|
||||||
|
return EVP_sha512();
|
||||||
|
default:
|
||||||
|
return NULL;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static int hash_length(SRP_HashAlgorithm alg)
|
||||||
|
{
|
||||||
|
switch (alg) {
|
||||||
|
case SRP_SHA1:
|
||||||
|
return SHA_DIGEST_LENGTH;
|
||||||
|
case SRP_SHA512:
|
||||||
|
return SHA512_DIGEST_LENGTH;
|
||||||
|
default:
|
||||||
|
return -1;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static BIGNUM *H_nn_pad(SRP_HashAlgorithm alg, const BIGNUM *n1, const BIGNUM *n2, int padded_len)
|
||||||
|
{
|
||||||
|
unsigned char buff[SHA512_DIGEST_LENGTH];
|
||||||
|
int len_n1 = BN_num_bytes(n1);
|
||||||
|
int len_n2 = BN_num_bytes(n2);
|
||||||
|
int nbytes = 2 * padded_len;
|
||||||
|
int offset_n1 = padded_len - len_n1;
|
||||||
|
int offset_n2 = nbytes - len_n2;
|
||||||
|
unsigned char *bin = (unsigned char *) malloc(nbytes);
|
||||||
|
EVP_MD_CTX *ctx = EVP_MD_CTX_new();
|
||||||
|
|
||||||
|
pw_log_debug("srp: H_nn_pad");
|
||||||
|
|
||||||
|
if (!bin)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
assert(len_n1 <= padded_len);
|
||||||
|
assert(len_n2 <= padded_len);
|
||||||
|
|
||||||
|
BN_bn2bin(n1, bin + offset_n1);
|
||||||
|
BN_bn2bin(n2, bin + offset_n2);
|
||||||
|
EVP_DigestInit_ex(ctx, hash_func(alg), NULL);
|
||||||
|
EVP_DigestUpdate(ctx, bin, nbytes);
|
||||||
|
EVP_DigestFinal_ex(ctx, buff, NULL);
|
||||||
|
EVP_MD_CTX_free(ctx);
|
||||||
|
free(bin);
|
||||||
|
|
||||||
|
return BN_bin2bn(buff, hash_length(alg), NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
static BIGNUM *calculate_x(SRP_HashAlgorithm alg, const uint8_t *salt, int salt_len,
|
||||||
|
const char *username, const unsigned char *password, int password_len)
|
||||||
|
{
|
||||||
|
unsigned char x[SHA512_DIGEST_LENGTH];
|
||||||
|
EVP_MD_CTX *ctx;
|
||||||
|
|
||||||
|
assert(salt_len == SRP_SALT_BYTES);
|
||||||
|
|
||||||
|
pw_log_debug("srp: calculate_x");
|
||||||
|
|
||||||
|
ctx = EVP_MD_CTX_new();
|
||||||
|
EVP_DigestInit_ex(ctx, hash_func(alg), NULL);
|
||||||
|
EVP_DigestUpdate(ctx, username, strlen(username));
|
||||||
|
EVP_DigestUpdate(ctx, (const uint8_t *) ":", 1);
|
||||||
|
EVP_DigestUpdate(ctx, password, password_len);
|
||||||
|
EVP_DigestFinal_ex(ctx, x, NULL);
|
||||||
|
EVP_MD_CTX_free(ctx);
|
||||||
|
|
||||||
|
ctx = EVP_MD_CTX_new();
|
||||||
|
EVP_DigestInit_ex(ctx, hash_func(alg), NULL);
|
||||||
|
EVP_DigestUpdate(ctx, (const uint8_t*) salt, salt_len);
|
||||||
|
EVP_DigestUpdate(ctx, x, hash_length(alg));
|
||||||
|
EVP_DigestFinal_ex(ctx, x, NULL);
|
||||||
|
EVP_MD_CTX_free(ctx);
|
||||||
|
|
||||||
|
return BN_bin2bn(x, hash_length(alg), NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void hash_num(SRP_HashAlgorithm alg, const BIGNUM *n, unsigned char *dest)
|
||||||
|
{
|
||||||
|
int nbytes = BN_num_bytes(n);
|
||||||
|
unsigned char *bin = (unsigned char *) malloc(nbytes);
|
||||||
|
EVP_MD_CTX *ctx = EVP_MD_CTX_new();
|
||||||
|
|
||||||
|
if (!bin)
|
||||||
|
return;
|
||||||
|
|
||||||
|
BN_bn2bin(n, bin);
|
||||||
|
EVP_DigestInit_ex(ctx, hash_func(alg), NULL);
|
||||||
|
EVP_DigestUpdate(ctx, bin, nbytes);
|
||||||
|
EVP_DigestFinal_ex(ctx, dest, NULL);
|
||||||
|
EVP_MD_CTX_free(ctx);
|
||||||
|
free(bin);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void calculate_M(SRP_HashAlgorithm alg, NGConstant *ng, unsigned char *dest, const char *I,
|
||||||
|
const uint8_t *salt, int salt_len, const uint8_t *A, int A_len, const uint8_t *B, int B_len,
|
||||||
|
const unsigned char *K)
|
||||||
|
{
|
||||||
|
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];
|
||||||
|
EVP_MD_CTX *ctx;
|
||||||
|
int i = 0;
|
||||||
|
int hash_len = hash_length(alg);
|
||||||
|
|
||||||
|
pw_log_debug("srp: calculate_M");
|
||||||
|
|
||||||
|
hash_num(alg, ng->N, H_N);
|
||||||
|
hash_num(alg, ng->g, H_g);
|
||||||
|
|
||||||
|
ctx = EVP_MD_CTX_new();
|
||||||
|
EVP_DigestInit_ex(ctx, hash_func(alg), NULL);
|
||||||
|
EVP_DigestUpdate(ctx, I, strlen(I));
|
||||||
|
EVP_DigestFinal_ex(ctx, H_I, NULL);
|
||||||
|
EVP_MD_CTX_free(ctx);
|
||||||
|
|
||||||
|
for (i=0; i < hash_len; i++)
|
||||||
|
H_xor[i] = H_N[i] ^ H_g[i];
|
||||||
|
|
||||||
|
ctx = EVP_MD_CTX_new();
|
||||||
|
EVP_DigestInit_ex(ctx, hash_func(alg), NULL);
|
||||||
|
|
||||||
|
EVP_DigestUpdate(ctx, H_xor, hash_len);
|
||||||
|
EVP_DigestUpdate(ctx, H_I, hash_len);
|
||||||
|
EVP_DigestUpdate(ctx, salt, salt_len);
|
||||||
|
EVP_DigestUpdate(ctx, A, A_len);
|
||||||
|
EVP_DigestUpdate(ctx, B, B_len);
|
||||||
|
EVP_DigestUpdate(ctx, K, hash_len);
|
||||||
|
|
||||||
|
EVP_DigestFinal_ex(ctx, dest, NULL);
|
||||||
|
EVP_MD_CTX_free(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void calculate_H_AMK(SRP_HashAlgorithm alg, unsigned char *dest, const uint8_t *A, int A_len,
|
||||||
|
const unsigned char *M, const unsigned char *K)
|
||||||
|
{
|
||||||
|
EVP_MD_CTX *ctx = EVP_MD_CTX_new();
|
||||||
|
|
||||||
|
EVP_DigestInit_ex(ctx, hash_func(alg), NULL);
|
||||||
|
|
||||||
|
EVP_DigestUpdate(ctx, A, A_len);
|
||||||
|
EVP_DigestUpdate(ctx, M, hash_length(alg));
|
||||||
|
EVP_DigestUpdate(ctx, K, hash_length(alg));
|
||||||
|
|
||||||
|
EVP_DigestFinal_ex(ctx, dest, NULL);
|
||||||
|
EVP_MD_CTX_free(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SRPUser *srp_user_new(SRP_HashAlgorithm 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 = (struct SRPUser *) malloc(sizeof(struct SRPUser));
|
||||||
|
int ulen = strlen(username) + 1;
|
||||||
|
|
||||||
|
if (!usr)
|
||||||
|
goto err_exit;
|
||||||
|
|
||||||
|
usr->hash_alg = alg;
|
||||||
|
usr->ng = new_ng(ng_type, n_hex, g_hex);
|
||||||
|
|
||||||
|
usr->a = BN_new();
|
||||||
|
usr->A = BN_new();
|
||||||
|
usr->S = BN_new();
|
||||||
|
|
||||||
|
if (!usr->ng || !usr->a || !usr->A || !usr->S)
|
||||||
|
goto err_exit;
|
||||||
|
|
||||||
|
usr->username = (char *) malloc(ulen);
|
||||||
|
usr->password = (unsigned char *) 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;
|
||||||
|
usr->len_A = 0;
|
||||||
|
|
||||||
|
return usr;
|
||||||
|
|
||||||
|
err_exit:
|
||||||
|
if (!usr)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
BN_free(usr->a);
|
||||||
|
BN_free(usr->A);
|
||||||
|
BN_free(usr->S);
|
||||||
|
if (usr->username)
|
||||||
|
free(usr->username);
|
||||||
|
if (usr->password) {
|
||||||
|
memset(usr->password, 0, usr->password_len);
|
||||||
|
free(usr->password);
|
||||||
|
}
|
||||||
|
free(usr);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void srp_user_start_authentication(struct SRPUser *usr, const char **username, const unsigned char **bytes_A, int *len_A)
|
||||||
|
{
|
||||||
|
BN_CTX *bn_ctx = BN_CTX_new();
|
||||||
|
|
||||||
|
pw_log_debug("srp: srp_user_start_authentication");
|
||||||
|
|
||||||
|
BN_rand(usr->a, 256, -1, 0);
|
||||||
|
BN_mod_exp(usr->A, usr->ng->g, usr->a, usr->ng->N, bn_ctx);
|
||||||
|
|
||||||
|
BN_CTX_free(bn_ctx);
|
||||||
|
|
||||||
|
*len_A = BN_num_bytes(usr->A);
|
||||||
|
*bytes_A = (const unsigned char *) malloc(*len_A);
|
||||||
|
|
||||||
|
if (!*bytes_A) {
|
||||||
|
pw_log_debug("srp: srp_user_start_authentication something went wrong");
|
||||||
|
*len_A = 0;
|
||||||
|
*bytes_A = 0;
|
||||||
|
*username = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
BN_bn2bin(usr->A, (unsigned char *) *bytes_A);
|
||||||
|
|
||||||
|
usr->bytes_A = *bytes_A;
|
||||||
|
usr->len_A = *len_A;
|
||||||
|
*username = usr->username;
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
BIGNUM *B = BN_bin2bn(bytes_B, len_B, NULL);
|
||||||
|
BIGNUM *u = 0;
|
||||||
|
BIGNUM *x = 0;
|
||||||
|
BIGNUM *k = 0;
|
||||||
|
BIGNUM *v = BN_new();
|
||||||
|
BIGNUM *tmp1 = BN_new();
|
||||||
|
BIGNUM *tmp2 = BN_new();
|
||||||
|
BIGNUM *tmp3 = BN_new();
|
||||||
|
BN_CTX *bn_ctx = BN_CTX_new();
|
||||||
|
|
||||||
|
pw_log_debug("srp: srp_user_process_challenge");
|
||||||
|
|
||||||
|
*len_M = 0;
|
||||||
|
*bytes_M = 0;
|
||||||
|
|
||||||
|
if (!bytes_s || !B || !v || !tmp1 || !tmp2 || !tmp3 || !bn_ctx) {
|
||||||
|
pw_log_debug("srp: BIGNUM init failed");
|
||||||
|
goto cleanup_and_exit;
|
||||||
|
}
|
||||||
|
k = H_nn_pad(usr->hash_alg, usr->ng->N, usr->ng->g, usr->ng->N_len);
|
||||||
|
if (!k) {
|
||||||
|
pw_log_debug("srp: couldn't calc k");
|
||||||
|
goto cleanup_and_exit;
|
||||||
|
}
|
||||||
|
u = H_nn_pad(usr->hash_alg, usr->A, B, usr->ng->N_len);
|
||||||
|
if (!u) {
|
||||||
|
pw_log_debug("srp: couldn't calc u");
|
||||||
|
goto cleanup_and_exit;
|
||||||
|
}
|
||||||
|
x = calculate_x(usr->hash_alg, bytes_s, len_s, usr->username, usr->password, usr->password_len);
|
||||||
|
if (!x) {
|
||||||
|
pw_log_debug("srp: couldn't calc x");
|
||||||
|
goto cleanup_and_exit;
|
||||||
|
}
|
||||||
|
/* SRP-6a safety check */
|
||||||
|
if (!BN_is_zero(B) && !BN_is_zero(u)) {
|
||||||
|
BN_mod_exp(v, usr->ng->g, x, usr->ng->N, bn_ctx);
|
||||||
|
|
||||||
|
/* S = (B - k*(g^x)) ^ (a + ux) */
|
||||||
|
BN_mul(tmp1, u, x, bn_ctx);
|
||||||
|
BN_add(tmp2, usr->a, tmp1); /* tmp2 = (a + ux) */
|
||||||
|
BN_mod_exp(tmp1, usr->ng->g, x, usr->ng->N, bn_ctx);
|
||||||
|
BN_mul(tmp3, k, tmp1, bn_ctx); /* tmp3 = k*(g^x) */
|
||||||
|
BN_sub(tmp1, B, tmp3); /* tmp1 = (B - K*(g^x)) */
|
||||||
|
BN_mod_exp(usr->S, tmp1, tmp2, usr->ng->N, bn_ctx);
|
||||||
|
|
||||||
|
hash_num(usr->hash_alg, usr->S, usr->session_key);
|
||||||
|
|
||||||
|
calculate_M(usr->hash_alg, usr->ng, usr->M, usr->username, bytes_s, len_s, usr->bytes_A, usr->len_A, bytes_B, len_B, usr->session_key);
|
||||||
|
calculate_H_AMK(usr->hash_alg, usr->H_AMK, usr->bytes_A, usr->len_A, usr->M, usr->session_key);
|
||||||
|
|
||||||
|
*bytes_M = usr->M;
|
||||||
|
if (len_M)
|
||||||
|
*len_M = hash_length(usr->hash_alg);
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup_and_exit:
|
||||||
|
BN_free(B);
|
||||||
|
BN_free(u);
|
||||||
|
BN_free(x);
|
||||||
|
BN_free(k);
|
||||||
|
BN_free(v);
|
||||||
|
BN_free(tmp1);
|
||||||
|
BN_free(tmp2);
|
||||||
|
BN_free(tmp3);
|
||||||
|
BN_CTX_free(bn_ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
int srp_user_is_authenticated(struct SRPUser *usr)
|
||||||
|
{
|
||||||
|
return usr->authenticated;
|
||||||
|
}
|
||||||
|
|
||||||
|
const unsigned char *srp_user_get_session_key(struct SRPUser *usr, int *key_length)
|
||||||
|
{
|
||||||
|
if (key_length)
|
||||||
|
*key_length = hash_length(usr->hash_alg);
|
||||||
|
|
||||||
|
return usr->session_key;
|
||||||
|
}
|
||||||
|
|
||||||
|
void srp_user_verify_session(struct SRPUser *usr, const unsigned char *bytes_HAMK)
|
||||||
|
{
|
||||||
|
if (memcmp(usr->H_AMK, bytes_HAMK, hash_length(usr->hash_alg)) == 0 )
|
||||||
|
usr->authenticated = 1;
|
||||||
|
}
|
||||||
31
src/modules/module-raop/srp.h
Normal file
31
src/modules/module-raop/srp.h
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
/* PipeWire */
|
||||||
|
/* SPDX-FileCopyrightText: Copyright © 2013 Tom Cocagne */
|
||||||
|
/* SPDX-License-Identifier: MIT */
|
||||||
|
|
||||||
|
struct SRPUser;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
SRP_NG_2048,
|
||||||
|
SRP_NG_3072
|
||||||
|
} SRP_NGType;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
SRP_SHA1,
|
||||||
|
SRP_SHA512
|
||||||
|
} SRP_HashAlgorithm;
|
||||||
|
|
||||||
|
struct SRPUser *srp_user_new(SRP_HashAlgorithm alg, SRP_NGType ng_type, const char *username,
|
||||||
|
const unsigned char *bytes_password, int len_password, const char *n_hex, const char *g_hex);
|
||||||
|
|
||||||
|
int get_N_len(SRP_NGType ng_type);
|
||||||
|
|
||||||
|
void srp_user_start_authentication(struct SRPUser *usr, const char **username, const unsigned char **bytes_A, int *len_A);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
int srp_user_is_authenticated(struct SRPUser *usr);
|
||||||
|
|
||||||
|
const unsigned char *srp_user_get_session_key(struct SRPUser *usr, int *key_length);
|
||||||
|
|
||||||
|
void srp_user_verify_session(struct SRPUser *usr, const unsigned char *bytes_HAMK);
|
||||||
183
src/modules/module-raop/tlv.c
Normal file
183
src/modules/module-raop/tlv.c
Normal file
|
|
@ -0,0 +1,183 @@
|
||||||
|
/* PipeWire */
|
||||||
|
/* SPDX-FileCopyrightText: Copyright © 2018 Maxim Kulkin */
|
||||||
|
/* SPDX-License-Identifier: MIT */
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
|
||||||
|
#include "tlv.h"
|
||||||
|
|
||||||
|
tlv_values_t * tlv_new(void)
|
||||||
|
{
|
||||||
|
tlv_values_t *values = malloc(sizeof(tlv_values_t));
|
||||||
|
if (!values)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
values->head = NULL;
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
|
void tlv_free(tlv_values_t *values)
|
||||||
|
{
|
||||||
|
if (!values)
|
||||||
|
return;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
return -2;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
69
src/modules/module-raop/tlv.h
Normal file
69
src/modules/module-raop/tlv.h
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
/* PipeWire */
|
||||||
|
/* SPDX-FileCopyrightText: Copyright © 2018 Maxim Kulkin */
|
||||||
|
/* SPDX-License-Identifier: MIT */
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#define TLV_ERROR_MEMORY -1
|
||||||
|
#define TLV_ERROR_INSUFFICIENT_SIZE -2
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
AP2_TLVType_Method = 0, // (integer) Method to use for pairing. See PairMethod
|
||||||
|
AP2_TLVType_Identifier = 1, // (UTF-8) Identifier for authentication
|
||||||
|
AP2_TLVType_Salt = 2, // (bytes) 16+ bytes of random salt
|
||||||
|
AP2_TLVType_PublicKey = 3, // (bytes) Curve25519, SRP public key or signed Ed25519 key
|
||||||
|
AP2_TLVType_Proof = 4, // (bytes) Ed25519 or SRP proof
|
||||||
|
AP2_TLVType_EncryptedData = 5, // (bytes) Encrypted data with auth tag at end
|
||||||
|
AP2_TLVType_State = 6, // (integer) State of the pairing process. 1=M1, 2=M2, etc.
|
||||||
|
AP2_TLVType_Error = 7, // (integer) Error code. Must only be present if error code is
|
||||||
|
// not 0. See AP2_TLVError
|
||||||
|
AP2_TLVType_RetryDelay = 8, // (integer) Seconds to delay until retrying a setup code
|
||||||
|
AP2_TLVType_Certificate = 9, // (bytes) X.509 Certificate
|
||||||
|
AP2_TLVType_Signature = 10, // (bytes) Ed25519
|
||||||
|
AP2_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
|
||||||
|
AP2_TLVType_FragmentData = 13, // (bytes) Non-last fragment of data. If length is 0,
|
||||||
|
// it's an ACK.
|
||||||
|
AP2_TLVType_FragmentLast = 14, // (bytes) Last fragment of data
|
||||||
|
AP2_TLVType_Flags = 19, // Added from airplay2_receiver
|
||||||
|
AP2_TLVType_Separator = 0xff,
|
||||||
|
} AP2_TLVType;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
AP2_TLVError_Unknown = 1, // Generic error to handle unexpected errors
|
||||||
|
AP2_TLVError_Authentication = 2, // Setup code or signature verification failed
|
||||||
|
AP2_TLVError_Backoff = 3, // Client must look at the retry delay TLV item and
|
||||||
|
// wait that many seconds before retrying
|
||||||
|
AP2_TLVError_MaxPeers = 4, // Server cannot accept any more pairings
|
||||||
|
AP2_TLVError_MaxTries = 5, // Server reached its maximum number of
|
||||||
|
// authentication attempts
|
||||||
|
AP2_TLVError_Unavailable = 6, // Server pairing method is unavailable
|
||||||
|
AP2_TLVError_Busy = 7, // Server is busy and cannot accept a pairing
|
||||||
|
// request at this time
|
||||||
|
} AP2_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;
|
||||||
|
|
||||||
|
tlv_values_t *tlv_new(void);
|
||||||
|
|
||||||
|
void tlv_free(tlv_values_t *values);
|
||||||
|
|
||||||
|
int tlv_add_value(tlv_values_t *values, uint8_t type, const uint8_t *value, size_t size);
|
||||||
|
|
||||||
|
tlv_t *tlv_get_value(const tlv_values_t *values, uint8_t type);
|
||||||
|
|
||||||
|
int tlv_format(const tlv_values_t *values, uint8_t *buffer, size_t *size);
|
||||||
|
|
||||||
|
int tlv_parse(const uint8_t *buffer, size_t length, tlv_values_t *values);
|
||||||
Loading…
Add table
Add a link
Reference in a new issue