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:
Christian Glombek 2023-09-24 17:04:13 +00:00
commit dbe69e1f9c
11 changed files with 1685 additions and 92 deletions

View file

@ -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'))

View file

@ -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 = {

View file

@ -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')

View file

@ -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

View file

@ -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;

View file

@ -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
} }

View 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;
}

View 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);

View 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;
}

View 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);